如何在 C# 中编写实现给定接口的通用容器类?
Posted
技术标签:
【中文标题】如何在 C# 中编写实现给定接口的通用容器类?【英文标题】:How can I write a generic container class that implements a given interface in C#? 【发布时间】:2010-10-25 06:30:34 【问题描述】:上下文:.NET 3.5,VS2008。我不确定这个问题的标题,所以也可以对标题发表评论:-)
场景如下:我有几个类,比如 Foo 和 Bar,它们都实现了以下接口:
public interface IStartable
void Start();
void Stop();
现在我想要一个容器类,它在其构造函数中获取一个 IEnumerable
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();
所以我的问题是:如何在不手动编写代码且不生成代码的情况下做到这一点?换句话说,我想要像下面这样的东西。
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
约束:
没有代码生成(即编译时没有真正的文本代码) 接口只有 void 方法,带或不带参数动机:
我有一个相当大的应用程序,有很多各种接口的插件。为每个接口手动编写一个“组容器”类会用类“重载”项目 手动写代码容易出错 对 IStartable 接口的任何添加或签名更新都将导致“组容器”类中的(手动)更改 学习我知道我必须在这里使用反射,但我宁愿使用强大的框架(如 Castle 的 DynamicProxy 或 RunSharp)来为我进行布线。
有什么想法吗?
【问题讨论】:
所以你不想要一个 StartableGroup 类?它有什么问题? 请问:为什么?这需要解决什么问题? (这可能会影响答案...)。 @Noldorin,@Marc Gravell,在原始问题中添加了动机。 重新评论您对参数的评论 - 它很容易完成,但我可能不得不将“foreach”展开到 IL 中。这是一个反射器的工作。如果你需要,我可能会填空,但不是现在(忙了一个小时左右)。让我知道你是否重视这个。 已更新以实现 args;请注意,它还没有尝试/最终处置 - 稍后会添加。 【参考方案1】:这不是很漂亮,但它似乎工作:
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
【讨论】:
我认为你有一个小错误(不会编译):而不是:Cache它不像基于反射的解决方案那样干净的界面,但一个非常简单和灵活的解决方案是创建一个 ForAll 方法,如下所示:
static void ForAll<T>(this IEnumerable<T> items, Action<T> action)
foreach (T item in items)
action(item);
并且可以这样调用:
arr.ForAll(x => x.Start());
【讨论】:
【参考方案3】:您可以继承List<T>
或其他一些集合类,并使用where
泛型类型约束将T
类型限制为仅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();
如果您不希望它是需要类型参数的泛型类,也可以这样声明该类。
public class StartableList : List<IStartable>, IStartable
...
您的示例使用代码将如下所示:
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
【讨论】:
我认为这不能回答问题。 @DonkeyMaster - 不,它没有回答确切的问题,但我认为如果我正确理解了这个问题,它是一个可能的选择。我的帖子提供了一个手动编写的解决方案,Marc Gravell 的优秀示例提供了一个(运行时)代码生成解决方案。我不知道没有任何一种方法可以做到这一点:原始发布者要求“无需手动编写代码且无需代码生成”的解决方案。 确实,正如@DonkeyMaster 所指出的,这并不能回答问题。它使代码更清晰,也许更优雅,但问题仍然存在:如何在运行时创建这样的代码,而不必在设计时编写(或生成)?【参考方案4】:Automapper
是一个很好的解决方案。它依赖于下面的LinFu
来创建一个实现接口的实例,但它负责一些水化,并在一个有点流利的api下混合。 LinFu
作者声称它实际上比 Castle
的 Proxy
更轻量级和更快。
【讨论】:
感谢您的提示,有时间我会研究一下。【参考方案5】:您可以等待 C# 4.0 并使用动态绑定。
这是一个好主意——我不得不多次为 IDisposable 实现它;当我想要处理很多东西时。不过要记住的一件事是如何处理错误。如果它记录并继续启动其他人,等等...你需要一些选项来给课程。
我不熟悉 DynamicProxy 以及如何在这里使用它。
【讨论】:
C# 4.0 暂时不会出现。甚至还没有 CTP!【参考方案6】:您可以使用“List”类及其方法“ForEach”。
var startables = new List<IStartable>( array_of_startables );
startables.ForEach( t => t.Start();
【讨论】:
这也是我想到的第一件事——但他要求实现上面的“GroupGenerator”类。【参考方案7】:如果我理解正确,您要求的是“GroupGenerator”的实现。
如果没有任何实际使用 CastleProxy 的经验,我的建议是使用 GetMethods() 来获取接口中列出的初始方法,然后使用 Reflection.Emit 动态创建一个新类型,并使用枚举对象的新方法和调用每个对应的方法。性能应该不会太差。
【讨论】:
以上是关于如何在 C# 中编写实现给定接口的通用容器类?的主要内容,如果未能解决你的问题,请参考以下文章