Come posso scrivere una class contenitore generica che implementa una determinata interfaccia in C #?

Contesto: .NET 3.5, VS2008. Non sono sicuro del titolo di questa domanda, quindi sentiti libero di commentare anche il titolo 🙂

Ecco lo scenario: ho diverse classi, dico Foo e Bar, tutte implementano la seguente interfaccia:

public interface IStartable { void Start(); void Stop(); } 

E ora mi piacerebbe avere una class contenitore, che ottiene un object IEnumerable come argomento nel suo costruttore. Questa class, a sua volta, dovrebbe anche implementare l’interfaccia IStartable:

 public class StartableGroup : IStartable // this is the container class { private readonly IEnumerable startables; public StartableGroup(IEnumerable startables) { this.startables = startables; } public void Start() { foreach (var startable in startables) { startable.Start(); } } public void Stop() { foreach (var startable in startables) { startable.Stop(); } } } 

Quindi la mia domanda è: come posso farlo senza scrivere manualmente il codice e senza generare codice? In altre parole, mi piacerebbe avere qualcosa come il seguente.

 var arr = new IStartable[] { new Foo(), new Bar("wow") }; var mygroup = GroupGenerator.Create(arr); mygroup.Start(); // --> calls Foo's Start and Bar's Start 

vincoli:

  • Nessuna generazione di codice (ovvero, nessun vero codice testuale in fase di compilazione)
  • L’interfaccia ha solo metodi void, con o senza argomenti

Motivazione:

  • Ho un’applicazione abbastanza grande, con molti plugin di varie interfacce. La scrittura manuale di una class “contenitore di gruppo” per ogni interfaccia “sovraccarica” ​​il progetto con le classi
  • La scrittura manuale del codice è soggetta a errori
  • Eventuali aggiunte o aggiornamenti delle firme all’interfaccia IStartable porteranno a modifiche (manuali) nella class “contenitore di gruppo”
  • Apprendimento

Capisco che devo usare la riflessione qui, ma preferirei usare una struttura robusta (come Castle’s DynamicProxy o RunSharp ) per fare il cablaggio per me.

qualche idea?

Questo non è carino, ma sembra funzionare:

 public static class GroupGenerator { public static T Create(IEnumerable items) where T : class { return (T)Activator.CreateInstance(Cache.Type, items); } private static class Cache where T : class { internal static readonly Type Type; static Cache() { if (!typeof(T).IsInterface) { throw new InvalidOperationException(typeof(T).Name + " is not an interface"); } AssemblyName an = new AssemblyName("tmp_" + typeof(T).Name); var asm = AppDomain.CurrentDomain.DefineDynamicAssembly( an, AssemblyBuilderAccess.RunAndSave); string moduleName = Path.ChangeExtension(an.Name,"dll"); var module = asm.DefineDynamicModule(moduleName, false); string ns = typeof(T).Namespace; if (!string.IsNullOrEmpty(ns)) ns += "."; var type = module.DefineType(ns + "grp_" + typeof(T).Name, TypeAttributes.Class | TypeAttributes.AnsiClass | TypeAttributes.Sealed | TypeAttributes.NotPublic); type.AddInterfaceImplementation(typeof(T)); var fld = type.DefineField("items", typeof(IEnumerable), FieldAttributes.Private); var ctor = type.DefineConstructor(MethodAttributes.Public, CallingConventions.HasThis, new Type[] { fld.FieldType }); var il = ctor.GetILGenerator(); // store the items il.Emit(OpCodes.Ldarg_0); il.Emit(OpCodes.Ldarg_1); il.Emit(OpCodes.Stfld, fld); il.Emit(OpCodes.Ret); foreach (var method in typeof(T).GetMethods()) { var args = method.GetParameters(); var methodImpl = type.DefineMethod(method.Name, MethodAttributes.Private | MethodAttributes.Virtual, method.ReturnType, Array.ConvertAll(args, arg => arg.ParameterType)); type.DefineMethodOverride(methodImpl, method); il = methodImpl.GetILGenerator(); if (method.ReturnType != typeof(void)) { il.Emit(OpCodes.Ldstr, "Methods with return values are not supported"); il.Emit(OpCodes.Newobj, typeof(NotSupportedException) .GetConstructor(new Type[] {typeof(string)})); il.Emit(OpCodes.Throw); continue; } // get the iterator var iter = il.DeclareLocal(typeof(IEnumerator)); il.Emit(OpCodes.Ldarg_0); il.Emit(OpCodes.Ldfld, fld); il.EmitCall(OpCodes.Callvirt, typeof(IEnumerable) .GetMethod("GetEnumerator"), null); il.Emit(OpCodes.Stloc, iter); Label tryFinally = il.BeginExceptionBlock(); // jump to "progress the iterator" Label loop = il.DefineLabel(); il.Emit(OpCodes.Br_S, loop); // process each item (invoke the paired method) Label doItem = il.DefineLabel(); il.MarkLabel(doItem); il.Emit(OpCodes.Ldloc, iter); il.EmitCall(OpCodes.Callvirt, typeof(IEnumerator) .GetProperty("Current").GetGetMethod(), null); for (int i = 0; i < args.Length; i++) { // load the arguments switch (i) { case 0: il.Emit(OpCodes.Ldarg_1); break; case 1: il.Emit(OpCodes.Ldarg_2); break; case 2: il.Emit(OpCodes.Ldarg_3); break; default: il.Emit(i < 255 ? OpCodes.Ldarg_S : OpCodes.Ldarg, i + 1); break; } } il.EmitCall(OpCodes.Callvirt, method, null); // progress the iterator il.MarkLabel(loop); il.Emit(OpCodes.Ldloc, iter); il.EmitCall(OpCodes.Callvirt, typeof(IEnumerator) .GetMethod("MoveNext"), null); il.Emit(OpCodes.Brtrue_S, doItem); il.Emit(OpCodes.Leave_S, tryFinally); // dispose iterator il.BeginFinallyBlock(); Label endFinally = il.DefineLabel(); il.Emit(OpCodes.Ldloc, iter); il.Emit(OpCodes.Brfalse_S, endFinally); il.Emit(OpCodes.Ldloc, iter); il.EmitCall(OpCodes.Callvirt, typeof(IDisposable) .GetMethod("Dispose"), null); il.MarkLabel(endFinally); il.EndExceptionBlock(); il.Emit(OpCodes.Ret); } Cache.Type = type.CreateType(); #if DEBUG // for inspection purposes... asm.Save(moduleName); #endif } } } 

Non è un’interfaccia così pulita come la soluzione basata sulla riflessione, ma una soluzione molto semplice e flessibile è quella di creare un metodo ForAll in questo modo:

 static void ForAll(this IEnumerable items, Action action) { foreach (T item in items) { action(item); } } 

E può essere chiamato così:

 arr.ForAll(x => x.Start()); 

È ansible sottoclass l’ List o qualche altra class di raccolta e utilizzare il vincolo di tipo generico in where limitare il tipo T per essere solo classi IStartable .

 class StartableList : List, IStartable where T : IStartable { public StartableList(IEnumerable arr) : base(arr) { } public void Start() { foreach (IStartable s in this) { s.Start(); } } public void Stop() { foreach (IStartable s in this) { s.Stop(); } } } 

Si potrebbe anche dichiarare la class in questo modo se non si desidera che sia una class generica che richiede un parametro di tipo.

 public class StartableList : List, IStartable { ... } 

Il tuo codice d’uso di esempio sarebbe quindi simile a questo:

 var arr = new IStartable[] { new Foo(), new Bar("wow") }; var mygroup = new StartableList(arr); mygroup.Start(); // --> calls Foo's Start and Bar's Start 

Automapper è una buona soluzione a questo. Si affida a LinFu sotto per creare un’istanza che implementa un’interfaccia, ma si prende cura di un po ‘dell’idratazione e mixin sotto una api piuttosto fluente. L’autore di LinFu afferma che in realtà è molto più leggero e più veloce del Proxy di Castle .

È ansible attendere C # 4.0 e utilizzare l’associazione dynamic.

Questa è una grande idea – ho dovuto implementarlo per IDisposable in diverse occasioni; quando voglio che molte cose siano disposte. Una cosa da tenere a mente è come verranno gestiti gli errori. Dovrebbe registrarsi e continuare ad avviarne altri, ecc … Avresti bisogno di alcune opzioni per dare la lezione.

Non ho familiarità con DynamicProxy e come potrebbe essere usato qui.

Puoi usare la class “List” e il loro metodo “ForEach”.

 var startables = new List( array_of_startables ); startables.ForEach( t => t.Start(); } 

Se ho capito bene, stai richiedendo un’implementazione di “GroupGenerator”.

Senza alcuna esperienza reale con CastleProxy la mia raccomandazione sarebbe quella di utilizzare GetMethods () per ottenere i metodi iniziali elencati nell’interfaccia e quindi creare un nuovo tipo al volo usando Reflection.Emit con i nuovi metodi che enumerano attraverso gli oggetti e chiamano ogni corrispondente metodo. Le prestazioni non dovrebbero essere pessime.