如何编写在C#中实现给定接口的通用容器类?

上下文:.NET 3.5,VS2008。 我不确定这个问题的标题,所以也可以自由评论标题:-)

这是场景:我有几个类,比如Foo和Bar,它们都实现了以下接口:

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

现在我想要一个容器类,它在构造函数中获取一个IEnumerable 作为参数。 反过来,这个类也应该实现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(); } } } 

所以我的问题是:如果不手动编写代码,并且没有代码生成,我怎么能这样做呢? 换句话说,我想要像以下一样。

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

约束:

  • 没有代码生成(即编译时没有真正的文本代码)
  • 接口只有void方法, 有或没有参数

动机:

  • 我有一个非常大的应用程序,有很多各种接口的插件。 手动为每个接口编写一个“组容器”类,用类“重载”项目
  • 手动编写代码很容易出错
  • 对IStartable接口的任何添加或签名更新都将导致“组容器”类中的(手动)更改
  • 学习

我知道我必须在这里使用reflection,但我宁愿使用一个健壮的框架(比如Castle的DynamicProxy或RunSharp )来为我做接线。

有什么想法吗?

这不是很漂亮,但似乎有效:

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

它不像基于reflection的解决方案那样干净,但是一个非常简单灵活的解决方案是创建一个ForAll方法,如下所示:

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

可以像这样调用:

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

您可以inheritanceList或其他一些集合类,并使用wheregenerics类型约束将T类型限制为仅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(); } } } 

如果你不希望它是一个需要类型参数的generics类,你也可以像这样声明这个类。

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

您的示例使用代码将如下所示:

 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是一个很好的解决方案。 它依赖于下面的LinFu来创建一个实现接口的实例,但它会处理一些水合作用,并在一个有点流畅的api下进行混合。 LinFu作者声称它实际上比CastleProxy更轻巧,更快。

您可以等待C#4.0并使用动态绑定。

这是个好主意 – 我必须多次为IDisposable实现这个function; 当我想要处理许多事情的时候。 但要记住的一件事是如何处理错误。 它应该记录并继续启动其他人等等……你需要一些选择来给这个课程。

我不熟悉DynamicProxy以及它如何在这里使用。

您可以使用“List”类及其方法“ForEach”。

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

如果我理解正确,您要求实施“GroupGenerator”。

没有任何CastleProxy的实际经验,我的建议是使用GetMethods()来获取界面中列出的初始方法,然后使用Reflection.Emit动态创建一个新类型,其中新方法枚举对象并调用每个对应的方法。 表现应该不会太糟糕。