Как сделать простой динамический прокси в C#

Я хочу построить динамический прокси-объект, чтобы добавить определенную функциональность к объекту.

в основном я хочу получить объект, обернуть его объектом, который выглядит идентично оригиналу, который я получил, и перехватить все вызовы.

class Wrapper : DynamicProxy// dynamic proxy is not a reall class, but i guess something like this exists...
{
    public static T Wrap(T obj)
    {
        return (T) new Wrapper(obj);
    }

    public override object InterceptCall(MethodInfo info, object[] args)
    {
        // do stuff
    }

}

Просто чтобы уточнить, я хочу сделать что-то похожее на фабрику каналов WCF...


Я добавляю вознаграждение, потому что мне нужен хороший способ для прокси-классов (не интерфейсов) и для обработки не виртуальных методов (как если бы я унаследовал и добавил метонд под ключевым словом "new"). Я уверен, что все это очень возможно, так как.Net делает это.

10 ответов

Решение

Я должен был написать это раньше, но не бери в голову.

В моей проблеме была особая "ошибка", которая мне нужна, чтобы иметь возможность прокси-классов, а не интерфейсов.

Есть два решения этого:

  1. Реальный прокси и друзья, в основном, подразумевают использование.Net Remoting. Требуется один для наследования от ContextBoundObject.

  2. Построение прокси с использованием System.Reflection.Emit, как это было сделано весной, вы также можете посмотреть на код их ProxyFactoryObject. Вот еще три статьи на эту тему.

Кстати, у второго подхода есть существенное ограничение, вы не можете использовать не виртуальные прокси.

Вы можете сделать это с помощью комбинации DynamicObject и ImpromptuInterface, но вам потребуется интерфейс, реализующий функции и свойства, которые вы хотите использовать для прокси.

public interface IDoStuff
{
    void Foo();
}

public class Wrapper<T> : DynamicObject
{
    private readonly T _wrappedObject;

    public static T1 Wrap<T1>(T obj) where T1 : class
    {
        if (!typeof(T1).IsInterface)
            throw new ArgumentException("T1 must be an Interface");

        return new Wrapper<T>(obj).ActLike<T1>();
    }

    //you can make the contructor private so you are forced to use the Wrap method.
    private Wrapper(T obj)
    {
        _wrappedObject = obj;
    }

    public override bool TryInvokeMember(InvokeMemberBinder binder, object[] args, out object result)
    {
        try
        {
            //do stuff here

            //call _wrappedObject object
            result = _wrappedObject.GetType().GetMethod(binder.Name).Invoke(_wrappedObject, args);
            return true;
        }
        catch
        {
            result = null;
            return false;
        }
    }
}

Вы можете, конечно, решить потерять безопасность типов и пойти с DynamicObject, как я показал, а затем отбросить кастинг.

Я сделал прозрачную расширяемую версию этого прокси-объекта и открыл ее здесь.

В дополнение к Castle.DynamicProxy на Github есть также LinFu.DynamicProxy.

.NET 6.0 добавил нового кандидата в Reflectionпространство имен: DispatchProxy . Команда объявляет об этом здесь . Пример использования содержится в статье.

Посмотрите на PostSharp. Я не знаю, как сделать то, что вы хотите, в vanilla .Net, но PostSharp предлагает такие вещи, как "OnMethodBoundaryAspect", которые можно использовать для замены или переноса кода внутри метода.

Я использовал его для ведения журналов, проверки параметров, обработки исключений и т. Д.

Существует бесплатная версия Community Edition, которая должна работать на вас. Он понадобится вам на вашем компьютере для разработки, а также на любом используемом вами сервере сборки.

Вот простой пример создания динамического прокси с использованием C# Emit

Вы также можете использовать AOP-фреймворк, такой как PostSharp

Другой вариант ContextBoundObject,

В CodeProject была статья 8-9 лет назад, в которой этот подход использовался для отслеживания вызовов методов.

Для добавления любой функциональности до и после каждой функции в классе, Real proxy является хорошим подходом.

Так что теперь в T может быть любой TestClass. Создайте такой экземпляр для TestClass-

var _instance = (object) DynamicProxy (TestClass).GetTransparentProxy ();

Код для динамического прокси

 class DynamicProxy<T> : RealProxy
    {
        readonly T decorated;

        public DynamicProxy(T decorated) : base(typeof(T))
        {
            this.decorated = decorated;
        }

        public override IMessage Invoke(IMessage msg)
        {
            var methodCall = msg as IMethodCallMessage;
            var methodInfo = methodCall.MethodBase as MethodInfo;
            string fullMethodName = $"{methodInfo.DeclaringType.Name}.{methodCall.MethodName}";

            try
            {
                var result = methodInfo.Invoke(decorated, methodCall.InArgs);

                return new ReturnMessage(result, null, 0, methodCall.LogicalCallContext, methodCall);
            }

            catch (Exception e)
            {
                return new ReturnMessage(e, methodCall);
            }
            finally
            {
            }
        }
    }

Вы можете сделать это, используя только DynamicObject из пространства имен System.Danymic, без использования каких-либо сторонних библиотек.

Пример кода:

      public class DynamicProxy: DynamicObject
    {
        private readonly T _object;

        // The inner dictionary.
        Dictionary<string, object> dictionary = new Dictionary<string, object>();

        // Getting a property.
        public override bool TryGetMember(GetMemberBinder binder, out object result)
        {
            return dictionary.TryGetValue(binder.Name, out result);
        }

        // Setting a property.
        // You can set up access control eg. if you don't want to 
        // set certain field, you can return false before putting
        // the value into the inner dictionary
        public override bool TrySetMember(SetMemberBinder binder, object value)
        {
            if (binder.Name.Equals("Rating")) return false;
            dictionary[binder.Name] = value;
            return true;
        }

        public DynamicProxy(T object)
        {
            _object = object;
            dictionary["Name"] = object.GetName();
            dictionary["Gender"] = object.GetGender();
            dictionary["Interests"] = object.GetInterests();
            dictionary["Rating"] = object.GetGeekRating();
        }

        public string GetName()
        {
            return (string)dictionary["Name"];
        }

        public int GetGeekRating()
        {
            return (int)dictionary["Rating"];
        }
}

Затем в классе драйвера:

      dynamic dynamicProxy = new DynamicProxy(person);

Таким образом, вы можете установить и получить поля с контролем доступа.

Вы не можете перехватить все вызовы статических, а не виртуальных или закрытых членов, если вы не заставите CLR подключаться к каждому вызову каждого метода / свойства для этого объекта и перенаправлять вызов на созданный вами поддельный. Этого можно добиться с помощью API.NET Profiler. TypeMock Isolator, например, использует его для мониторинга выполнения приложения, и когда метод вызывается, CLR уведомляет изолятор typemock, который позволяет Isolator полностью переопределить исходный класс.

Другие вопросы по тегам