我如何编写实现C#中的给定的接口的通用容器类? [英] How can I write a generic container class that implements a given interface in C#?
问题描述
上下文:.NET 3.5,VS2008。我不知道这个问题的标题,可以随意对标题发表评论,太:-)
Context: .NET 3.5, VS2008. I'm not sure about the title of this question, so feel free to comment about the title, too :-)
下面的情况:我有几个班,说Foo和Bar,它们都实现以下接口:
Here's the scenario: I have several classes, say Foo and Bar, all of them implement the following interface:
public interface IStartable
{
void Start();
void Stop();
}
现在,我想有一个容器类,它得到一个IEnumerable< IStartable>作为其构造方法的参数。这个类,反过来,还应该实现IStartable接口:
And now I'd like to have a container class, which gets an IEnumerable<IStartable> as an argument in its constructor. This class, in turn, should also implement the IStartable interface:
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();
}
}
}
所以我的问题是:如何能做到这一点,而无需手动编写code,没有code一代?换句话说,我想有somethig像下面这样。
So my question is: how can I do it without manually writing the code, and without code generation? In other words, I'd like to have somethig like the following.
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
约束:
- 无code代(即,没有真正的文本code在编译时)
- 端口只有void的方法,带或不带参数
- No code generation (that is, no real textual code at compile time)
- The interface has only void methods, with or without arguments
动机:
- 我有一个pretty大型应用程序,有很多不同的接口插件。手动写组容器类为每个接口重载与类项目
- 手动写code是容易出错
- 任何添加或签名更新到IStartable接口将导致在组容器(手动)更改类
- 学习
我明白,我要在这里使用反射,但我宁愿用一个强有力的框架(如城堡的 DynamicProxy 一个>或 RunSharp )做配线我。
I understand that I have to use reflection here, but I'd rather use a robust framework (like Castle's DynamicProxy or RunSharp) to do the wiring for me.
有什么想法?
推荐答案
这是不是pretty,但它似乎工作:
This isn't pretty, but it seems to work:
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
}
}
}
这篇关于我如何编写实现C#中的给定的接口的通用容器类?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!