Подтвердить что ты не робот

Динамическое создание прокси-класса

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

Если, например, у меня есть следующий класс, реализующий интерфейс:

interface IMyInterface
{
    void MyProcedure();
}

class MyClass : IMyInterface
{
    void MyProcedure()
    {
        Console.WriteLine("Hello World");
    }
}

Чтобы перехватить методы для этого класса для их регистрации, я создаю еще один класс (моя версия прокси-класса), который реализует один и тот же интерфейс, но содержит ссылку на "настоящий" класс. Этот класс выполняет действие (например, протоколирование), а затем вызывает тот же метод в реальном классе.

Например:

class ProxyClass : IMyInterface
{
    private IMyInterface RealClass { get; set; }

    void MyProcedure()
    {
        // Log the call
        Console.WriteLine("Logging..");

        // Call the 'real' method
        RealClass.MyProcedure();
    }
}

Затем вызывающий абонент вызывает все методы в прокси-классе (я использую базовый home- brew контейнер IoC, чтобы ввести класс прокси вместо реального класса). Я использую этот метод, потому что я хотел бы иметь возможность поменять RealClass во время выполнения на другой класс, реализующий тот же интерфейс.

Есть ли способ создать ProxyClass во время выполнения и заполнить его свойство RealClass, чтобы он мог использоваться как прокси для реального класса? Есть ли простой способ сделать это или мне нужно использовать что-то вроде Reflection.Emit и сгенерировать MSIL?

4b9b3361

Ответ 1

Посмотрите System.Runtime.Remoting.Proxies.RealProxy. Вы можете использовать это, чтобы создать экземпляр, который является целевым типом с точки зрения вызывающего. RealProxy.Invoke предоставляет точку, из которой вы можете просто вызвать целевой метод для базового типа или выполнить дополнительную обработку до/после вызова (например, ведение журнала).

Вот пример прокси-сервера, который регистрируется на консоли до/после каждого вызова метода:

public class LoggingProxy<T> : RealProxy
{
    private readonly T _instance;

    private LoggingProxy(T instance)
        : base(typeof(T))
    {
        _instance = instance;
    }

    public static T Create(T instance)
    {
        return (T)new LoggingProxy<T>(instance).GetTransparentProxy();
    }

    public override IMessage Invoke(IMessage msg)
    {
        var methodCall = (IMethodCallMessage)msg;
        var method = (MethodInfo)methodCall.MethodBase;

        try
        {
            Console.WriteLine("Before invoke: " + method.Name);
            var result = method.Invoke(_instance, methodCall.InArgs);
            Console.WriteLine("After invoke: " + method.Name);
            return new ReturnMessage(result, null, 0, methodCall.LogicalCallContext, methodCall);
        }
        catch (Exception e)
        {
            Console.WriteLine("Exception: " + e);
            if (e is TargetInvocationException && e.InnerException != null)
            {
                return new ReturnMessage(e.InnerException, msg as IMethodCallMessage);
            }

            return new ReturnMessage(e, msg as IMethodCallMessage);
        }
    }
}

Вот как вы его используете:

IMyInterface intf = LoggingProxy<IMyInterface>.Create(new MyClass());
intf.MyProcedure();

Результатом вывода на консоль будет:

Перед вызовом: MyProcedure
Hello World
После вызова: MyProcedure

Ответ 2

Вы можете использовать динамические объекты, как описано в этом вопросе, но для динамически генерируемого, строго типизированного объекта вам придется использовать Reflection.Emit, так как вы подозревали. В этом блоге есть пример кода, показывающий динамическое создание и создание экземпляра типа.

Я читал, что Roslyn имеет функции, которые упрощают создание динамических прокси, поэтому, возможно, посмотрите и там.

Ответ 3

Возможно, я неправильно понял вопрос, как насчет конструктора?

class ProxyClass : IMyInterface
{
    public ProxyClass(IMyInterface someInterface)
    {
        RealClass = someInterface;
    }
   // Your other code...
}

Ответ 4

Я бы не рекомендовал это делать. Обычно вы используете некоторые известные библиотеки, такие как Castle или EntLib. Для некоторых сложных классов может быть довольно сложной задачей динамически генерировать прокси. Вот пример того, как использовать полиморфизм "Is". Для этого вы должны объявить все свои методы в базе виртуальными. То, как вы пытались это сделать ( "Has" ), также возможно, но для меня выглядит более сложным.

public class A
{
    public virtual void B()
    {
        Console.WriteLine("Original method was called.");
    }
}

class Program
{

    static void Main(string[] args)
    {
        // Create simple assembly to hold our proxy
        AssemblyName assemblyName = new AssemblyName();
        assemblyName.Name = "DynamicORMapper";
        AppDomain thisDomain = Thread.GetDomain();
        var asmBuilder = thisDomain.DefineDynamicAssembly(assemblyName,
                     AssemblyBuilderAccess.Run);

        var modBuilder = asmBuilder.DefineDynamicModule(
                     asmBuilder.GetName().Name, false);

        // Create a proxy type
        TypeBuilder typeBuilder = modBuilder.DefineType("ProxyA",
           TypeAttributes.Public |
           TypeAttributes.Class |
           TypeAttributes.AutoClass |
           TypeAttributes.AnsiClass |
           TypeAttributes.BeforeFieldInit |
           TypeAttributes.AutoLayout,
           typeof(A));
        MethodBuilder methodBuilder = typeBuilder.DefineMethod("B", MethodAttributes.Public | MethodAttributes.Virtual | MethodAttributes.ReuseSlot);
        typeBuilder.DefineMethodOverride(methodBuilder, typeof(A).GetMethod("B"));


        // Generate a Console.Writeline() and base.B() calls.
        ILGenerator ilGenerator = methodBuilder.GetILGenerator();
        ilGenerator.Emit(OpCodes.Ldarg_0);
        ilGenerator.EmitWriteLine("We caught an invoke! B method was called.");

        ilGenerator.EmitCall(OpCodes.Call, typeBuilder.BaseType.GetMethod("B"), new Type[0]);
        ilGenerator.Emit(OpCodes.Ret);

        //Create a type and casting it to A. 
        Type type = typeBuilder.CreateType();
        A a = (A) Activator.CreateInstance(type);

        // Test it
        a.B();
        Console.ReadLine();
    }
}