Динамическое создание прокси-класса
Я пытаюсь создать прокси-класс динамически. Я знаю, что для этого есть несколько очень хороших рамок, но это чисто домашний проект в качестве учебного упражнения, поэтому я бы хотел сделать это сам.
Если, например, у меня есть следующий класс, реализующий интерфейс:
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();
}
}
Затем вызывающая сторона вместо этого вызывает все методы прокси-класса (я использую базовый контейнер IoC домашнего пивоварения, чтобы внедрить прокси-класс вместо реального класса). Я использую этот метод, потому что я хотел бы иметь возможность поменять RealClass
во время выполнения другому классу, реализующему тот же интерфейс.
Есть ли способ создать ProxyClass
во время выполнения и заполнить его RealClass
свойство, чтобы его можно было использовать как прокси для реального класса? Есть ли простой способ сделать это или мне нужно использовать что-то вроде Reflection.Emit
и генерировать MSIL?
4 ответа
Посмотрите 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
Привет, мир
После вызова: MyProcedure
Я бы не рекомендовал делать это. Обычно вы используете некоторые известные библиотеки, такие как Castle или EntLib. Для некоторых сложных классов это может быть довольно сложной задачей для динамической генерации прокси. Вот пример того, как это делается с использованием полиморфизма "Есть". Для этого вы должны объявить все ваши методы в базе как виртуальные. Способ, которым вы пытались это сделать ("Имеет") также возможен, но для меня выглядит более сложным.
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();
}
}
Вы можете использовать динамические объекты, как описано в этом вопросе, но для динамически генерируемого объекта со строгой типизацией вам придется использовать Reflection.Emit
, как вы и подозревали. В этом блоге есть пример кода, показывающий динамическое создание и создание экземпляра типа.
Я читал, что у Roslyn есть функции, которые облегчают создание динамических прокси-серверов, так что, возможно, посмотрите и здесь.
Может быть, я неправильно понял вопрос, а как насчет конструктора?
class ProxyClass : IMyInterface
{
public ProxyClass(IMyInterface someInterface)
{
RealClass = someInterface;
}
// Your other code...
}