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:
Motivazione:
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.