Création d’une classe pour une interface à l’exécution, en C #

Je cherche à prendre un ensemble d’objects, disons qu’il y a 3 objects vivants pour le moment, qui implémentent tous une interface commune, puis que ces objects sont intégrés dans un quasortingème object, implémentant également la même interface.

Les implémentations de méthodes et propriétés du quasortingème object appellent simplement les bits pertinents de ces 3 objects sous-jacents. Je sais qu’il y aura des cas où cela n’aura pas de sens de faire cela, mais c’est pour une architecture de multidiffusion de services, donc il y a déjà une bonne série de limitations en place.

Ma question est de savoir par où commencer. La génération de ce quasortingème object doit être faite en mémoire, au moment de l’exécution, alors je pense à Reflection.Emit , malheureusement, je n’ai pas assez d’expérience avec cela pour savoir même par où commencer.

Dois-je construire un assemblage en mémoire? Cela semble être le cas, mais je voudrais juste un rapide indicateur de l’endroit où je devrais commencer.

Fondamentalement, je cherche à prendre une interface et une liste d’instances d’objects implémentant toutes cette interface, et à construire un nouvel object, ainsi qu’à cette interface, qui devrait “multidiffuser” tous les appels de méthode et les propriétés d’access à tous les objects sous-jacents, à moins autant que possible. Il y aura énormément de problèmes avec les exceptions et autres, mais je m’attaquerai à ces problèmes lorsque j’arriverai à les résoudre.

Ceci est pour une architecture orientée services, où je voudrais avoir un code existant qui prend, par exemple, un service de journalisation, pour accéder maintenant à plusieurs services de journalisation, sans avoir à changer le code qui utilise les services. Au lieu de cela, j’aimerais créer à l’exécution un générateur de service de journalisation qui appelle simplement en interne les méthodes appropriées sur plusieurs objects sous-jacents.

Ceci est pour .NET 3.5 et C #.

(Je justifie une réponse ici en ajoutant un contexte / des informations supplémentaires)

Oui, pour le moment, Reflection.Emit est le seul moyen de résoudre ce problème.

Dans .NET 4.0, la classe Expression a été étendue pour prendre en charge les boucles et les blocs d’instructions. Par conséquent, pour une utilisation avec une seule méthode , une Expression compilée serait une bonne idée. Mais même cela ne supportera pas les interfaces multi-méthodes (juste les delegates mono-méthodes).

Heureusement, j’ai déjà fait cela auparavant; voir Comment puis-je écrire une classe de conteneur générique qui implémente une interface donnée en C #?

Je posterai ma propre implémentation ici, si quelqu’un est intéressé.

Ceci est fortement influencé et copié de la réponse de Marc, ce que j’ai accepté.

Le code peut être utilisé pour envelopper un ensemble d’objects, tous implémentant une interface commune, à l’intérieur d’un nouvel object, implémentant également cette interface. Lorsque vous accédez aux méthodes et aux propriétés de l’object renvoyé, les méthodes et les propriétés correspondantes des objects sous-jacents sont utilisées de la même manière.

Ici, il y a des dragons : c’est pour un usage spécifique. Cela pourrait poser des problèmes étranges, en particulier dans la mesure où le code ne garantit pas que tous les objects sous-jacents reçoivent exactement les mêmes objects que l’appelé le transmet (ou n’interdit pas à l’un des objects sous-jacents de jouer avec les arguments). et pour les méthodes renvoyant une valeur, seule la dernière valeur renvoyée est renvoyée. En ce qui concerne les arguments out / ref, je n’ai même pas testé comment cela fonctionne, mais ce n’est probablement pas le cas. Tu étais prévenu.

 #region Using using System; using System.Linq; using System.Diagnostics; using System.Reflection; using System.Reflection.Emit; using LVK.Collections; #endregion namespace LVK.IoC { ///  /// This class implements a service wrapper that can wrap multiple services into a single multicast /// service, that will in turn dispatch all method calls down into all the underlying services. ///  ///  /// This code is heavily influenced and copied from Marc Gravell's implementation which he /// posted on Stack Overflow here: http://stackoverflow.com/questions/847809 ///  public static class MulticastService { ///  /// Wrap the specified services in a single multicast service object. ///  ///  /// The type of service to implement a multicast service for. ///  ///  /// The underlying service objects to multicast all method calls to. ///  ///  /// The multicast service instance. ///  ///  ///  is null. /// - or - ///  contains a null reference. ///  ///  ///  is not an interface type. ///  public static TService Wrap(params TService[] services) where TService: class { return (TService)Wrap(typeof(TService), (Object[])services); } ///  /// Wrap the specified services in a single multicast service object. ///  ///  /// The  object for the service interface to implement a multicast service for. ///  ///  /// The underlying service objects to multicast all method calls to. ///  ///  /// The multicast service instance. ///  ///  ///  is null. /// - or - ///  is null. /// - or - ///  contains a null reference. ///  ///  ///  is not an interface type. ///  ///  /// One or more of the service objects in  does not implement /// the  interface. ///  public static Object Wrap(Type serviceInterfaceType, params Object[] services) { #region Parameter Validation if (Object.ReferenceEquals(null, serviceInterfaceType)) throw new ArgumentNullException("serviceInterfaceType"); if (!serviceInterfaceType.IsInterface) throw new ArgumentException("serviceInterfaceType"); if (Object.ReferenceEquals(null, services) || services.Length == 0) throw new ArgumentNullException("services"); foreach (var service in services) { if (Object.ReferenceEquals(null, service)) throw new ArgumentNullException("services"); if (!serviceInterfaceType.IsAssignableFrom(service.GetType())) throw new InvalidOperationException("One of the specified services does not implement the specified service interface"); } #endregion if (services.Length == 1) return services[0]; AssemblyName assemblyName = new AssemblyName(Ssortingng.Format("tmp_{0}", serviceInterfaceType.FullName)); Ssortingng moduleName = Ssortingng.Format("{0}.dll", assemblyName.Name); Ssortingng ns = serviceInterfaceType.Namespace; if (!Ssortingng.IsNullOrEmpty(ns)) ns += "."; var assembly = AppDomain.CurrentDomain.DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.RunAndSave); var module = assembly.DefineDynamicModule(moduleName, false); var type = module.DefineType(Ssortingng.Format("{0}Multicast_{1}", ns, serviceInterfaceType.Name), TypeAtsortingbutes.Class | TypeAtsortingbutes.AnsiClass | TypeAtsortingbutes.Sealed | TypeAtsortingbutes.NotPublic); type.AddInterfaceImplementation(serviceInterfaceType); var ar = Array.CreateInstance(serviceInterfaceType, services.Length); for (Int32 index = 0; index < services.Length; index++) ar.SetValue(services[index], index); // Define _Service0..N-1 private service fields FieldBuilder[] fields = new FieldBuilder[services.Length]; var cab = new CustomAttributeBuilder( typeof(DebuggerBrowsableAttribute).GetConstructor(new Type[] { typeof(DebuggerBrowsableState) }), new Object[] { DebuggerBrowsableState.Never }); for (Int32 index = 0; index < services.Length; index++) { fields[index] = type.DefineField(String.Format("_Service{0}", index), serviceInterfaceType, FieldAttributes.Private); // Ensure the field don't show up in the debugger tooltips fields[index].SetCustomAttribute(cab); } // Define a simple constructor that takes all our services as arguments var ctor = type.DefineConstructor(MethodAttributes.Public, CallingConventions.HasThis, Sequences.Repeat(serviceInterfaceType, services.Length).ToArray()); var generator = ctor.GetILGenerator(); // Store each service into its own fields for (Int32 index = 0; index < services.Length; index++) { generator.Emit(OpCodes.Ldarg_0); switch (index) { case 0: generator.Emit(OpCodes.Ldarg_1); break; case 1: generator.Emit(OpCodes.Ldarg_2); break; case 2: generator.Emit(OpCodes.Ldarg_3); break; default: generator.Emit(OpCodes.Ldarg, index + 1); break; } generator.Emit(OpCodes.Stfld, fields[index]); } generator.Emit(OpCodes.Ret); // Implement all the methods of the interface foreach (var method in serviceInterfaceType.GetMethods()) { var args = method.GetParameters(); var methodImpl = type.DefineMethod(method.Name, MethodAttributes.Private | MethodAttributes.Virtual, method.ReturnType, (from arg in args select arg.ParameterType).ToArray()); type.DefineMethodOverride(methodImpl, method); // Generate code to simply call down into each service object // Any return values are discarded, except the last one, which is returned generator = methodImpl.GetILGenerator(); for (Int32 index = 0; index < services.Length; index++) { generator.Emit(OpCodes.Ldarg_0); generator.Emit(OpCodes.Ldfld, fields[index]); for (Int32 paramIndex = 0; paramIndex < args.Length; paramIndex++) { switch (paramIndex) { case 0: generator.Emit(OpCodes.Ldarg_1); break; case 1: generator.Emit(OpCodes.Ldarg_2); break; case 2: generator.Emit(OpCodes.Ldarg_3); break; default: generator.Emit((paramIndex < 255) ? OpCodes.Ldarg_S : OpCodes.Ldarg, paramIndex + 1); break; } } generator.Emit(OpCodes.Callvirt, method); if (method.ReturnType != typeof(void) && index < services.Length - 1) generator.Emit(OpCodes.Pop); // discard N-1 return values } generator.Emit(OpCodes.Ret); } return Activator.CreateInstance(type.CreateType(), services); } } } 

Avez-vous vraiment besoin de créer l’assembly au moment de l’exécution?

Vous n’en avez probablement pas besoin.

c # vous donne l’action , l’opérateur est et lambda / delegates …