web-dev-qa-db-fra.com

Création dynamique d'une classe de proxy

J'essaie de créer une classe de proxy de manière dynamique. Je sais qu’il existe de très bons cadres pour ce faire, mais il s’agit là d’un projet d’animal familier en tant qu’exercice d’apprentissage, donc je voudrais le faire moi-même.

Si, par exemple, la classe suivante implémente une interface:

interface IMyInterface
{
    void MyProcedure();
}

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

Pour intercepter les méthodes de cette classe afin de les consigner, je crée une autre classe (ma version d'une classe proxy) qui implémente la même interface mais contient une référence à la classe 'réelle'. Cette classe effectue une action (par exemple, la journalisation), puis appelle la même méthode sur la classe réelle.

Par exemple:

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

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

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

L'appelant appelle alors toutes les méthodes de la classe proxy à la place (j'utilise un conteneur IoC de base home-brew pour injecter la classe proxy à la place de la classe réelle). J'utilise cette méthode car j'aimerais pouvoir échanger RealClass au moment de l'exécution vers une autre classe implémentant la même interface.

Existe-t-il un moyen de créer ProxyClass au moment de l'exécution et de renseigner sa propriété RealClass afin qu'elle puisse être utilisée comme proxy pour la classe réelle? Existe-t-il un moyen simple de faire cela ou dois-je utiliser quelque chose comme Reflection.Emit et générer le MSIL?

35
Dave S

Regardez System.Runtime.Remoting.Proxies.RealProxy . Vous pouvez l'utiliser pour créer une instance qui semble être le type cible du point de vue de l'appelant. RealProxy.Invoke fournit un point à partir duquel vous pouvez simplement appeler la méthode cible sur le type sous-jacent ou effectuer un traitement supplémentaire avant/après l'appel (enregistrement, par exemple).

Voici un exemple de proxy qui se connecte à la console avant/après chaque appel de méthode:

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);
        }
    }
}

Voici comment vous l'utiliseriez:

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

La sortie vers la console serait alors:

Avant d'invoquer: MyProcedure
Bonjour le monde
Après invocation: MyProcedure

50
bradmo

Vous pouvez utiliser des objets dynamiques comme décrit dans cette question , mais pour un objet fortement typé, généré dynamiquement, vous devez utiliser Reflection.Emit, comme vous le supposiez. Ce blog a un exemple de code montrant la création dynamique et l'instanciation d'un Type.

J'ai lu que Roslyn présente des fonctionnalités facilitant la création de procurations dynamiques, alors jetez-y un coup d'œil également.

1
Steve Wilkes

Je ne recommanderais pas cela. Vous utilisez généralement des bibliothèques connues telles que Castle ou EntLib. Pour certaines classes compliquées, générer un proxy de manière dynamique peut s'avérer un défi. Voici un exemple de cela utilisant le polymorphisme "Is". Pour cela, vous devez déclarer toutes vos méthodes en base comme virtuelles. La façon dont vous avez essayé de faire cela ("A") est également possible, mais pour moi, cela semble plus compliqué.

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();
    }
}
0
Archeg

J'ai peut-être mal compris la question, qu'en est-il d'un constructeur?

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