usar una propiedades esperaba descriptores declarar crear como clase atributos agregar acceso c# generics reflection interface containers

propiedades - ¿Cómo puedo escribir una clase de contenedor genérica que implemente una interfaz dada en C#?



propiedades get c# (7)

Esto no es bonito, pero parece funcionar:

public static class GroupGenerator { public static T Create<T>(IEnumerable<T> items) where T : class { return (T)Activator.CreateInstance(Cache<T>.Type, items); } private static class Cache<T> 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<T>), 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<T>)); il.Emit(OpCodes.Ldarg_0); il.Emit(OpCodes.Ldfld, fld); il.EmitCall(OpCodes.Callvirt, typeof(IEnumerable<T>) .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<T>) .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<T>.Type = type.CreateType(); #if DEBUG // for inspection purposes... asm.Save(moduleName); #endif } } }

Contexto: .NET 3.5, VS2008. No estoy seguro sobre el título de esta pregunta, así que no dude en comentar sobre el título :-)

Este es el escenario: tengo varias clases, digamos Foo y Bar, todas ellas implementan la siguiente interfaz:

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

Y ahora me gustaría tener una clase contenedora, que obtiene un IEnumerable <IStartable> como argumento en su constructor. Esta clase, a su vez, también debe implementar la interfaz IStartable:

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

Entonces mi pregunta es: ¿cómo puedo hacerlo sin escribir el código manualmente, y sin generación de código? En otras palabras, me gustaría tener algo así como el siguiente.

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

Restricciones:

  • Sin generación de código (es decir, sin código de texto real en tiempo de compilación)
  • La interfaz solo tiene métodos vacíos, con o sin argumentos

Motivación:

  • Tengo una aplicación bastante grande, con muchos complementos de varias interfaces. Escribir manualmente una clase de "contenedor de grupo" para cada interfaz "sobrecarga" el proyecto con clases
  • Manualmente escribir el código es propenso a errores
  • Cualquier adición o actualización de firma a la interfaz IStartable dará lugar a cambios (manuales) en la clase "contenedor de grupo"
  • Aprendizaje

Entiendo que tengo que usar la reflexión aquí, pero prefiero usar un marco robusto (como DynamicProxy de DynamicProxy o RunSharp ) para hacer el cableado para mí.

¿Alguna idea?


No es una interfaz tan limpia como la solución basada en la reflexión, pero una solución muy simple y flexible es crear un método ForAll como ese:

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

Y se puede llamar así:

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


Podría subclasificar la List<T> o alguna otra clase de colección y usar la restricción de tipo genérico where para limitar el tipo T para que sea solo IStartable clase IStartable .

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

También podría declarar la clase así si no quisiera que fuera una clase genérica que requiera un parámetro de tipo.

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

Su código de uso de muestra se vería así:

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


Puede esperar C # 4.0 y usar enlace dinámico.

Esta es una gran idea: he tenido que implementar esto para IDisposable en varias ocasiones; cuando quiero que se eliminen muchas cosas. Una cosa a tener en cuenta es cómo se manejarán los errores. Si se registra y sigue iniciando a otros, etc. Necesitará algunas opciones para impartir la clase.

No estoy familiarizado con DynamicProxy y cómo podría usarse aquí.


Puede usar la clase "Lista" y su método "ForEach".

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


Si entiendo correctamente, está solicitando una implementación del "GroupGenerator".

Sin ninguna experiencia real con CastleProxy mi recomendación sería usar GetMethods () para obtener los métodos iniciales listados en la interfaz y luego crear un nuevo tipo sobre la marcha usando Reflection. Emitir con los nuevos métodos que enumeran a través de los objetos y llamar a cada uno método. El rendimiento no debería ser tan malo.


Automapper es una buena solución para esto. Se basa en LinFu debajo para crear una instancia que implementa una interfaz, pero se encarga de la hidratación y mixinas bajo una API apta. El autor de LinFu afirma que en realidad es mucho más liviano y más rápido que Castle ''s Proxy .