可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
Context: .NET 3.5, VS2008. I'm not sure about the title of this question, so feel free to comment about the title, too :-)
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();
}
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();
}
}
}
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
Constraints:
- No code generation (that is, no real textual code at compile time)
- The interface has only void methods, with or without arguments
Motivation:
- I have a pretty large application, with a lot of plugins of various interfaces. Manually writing a "group container" class for each interface "overloads" the project with classes
- Manually writing the code is error prone
- Any additions or signature updates to the IStartable interface will lead to (manual) changes in the "group container" class
- Learning
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.
Any thoughts?
回答1:
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
}
}
}
回答2:
It's not as clean an interface as the reflection based solution, but a very simple and flexible solution is to create a ForAll method like so:
static void ForAll<T>(this IEnumerable<T> items, Action<T> action)
{
foreach (T item in items)
{
action(item);
}
}
And can be called like so:
arr.ForAll(x => x.Start());
回答3:
You could subclass List<T>
or some other collection class and use the where
generic type constraint to limit the T
type to be only IStartable
classes.
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();
}
}
}
You could also declare the class like this if you didn't want it to be a generic class requiring a type parameter.
public class StartableList : List<IStartable>, IStartable
{ ... }
Your sample usage code would then look something like this:
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
回答4:
Automapper
is a good solution to this. It relies on LinFu
underneath to create an instance that implements an interface, but it takes care of some of the hydration, and mixins under a somewhat fluent api. The LinFu
author claims it is actually much more lightweight and faster than Castle
's Proxy
.
回答5:
You could wait for C# 4.0 and use dynamic binding.
This is a great idea - I've had to implement this for IDisposable on several occasions; when I want many things to be disposed. One thing to keep in mind though is how errors will be handled. Should it log and keep starting others, etc... You'd need some options to give the class.
I'm not familiar with DynamicProxy and how it could be used here.
回答6:
You can use the "List" class and their method "ForEach".
var startables = new List<IStartable>( array_of_startables );
startables.ForEach( t => t.Start(); }
回答7:
If I understand correctly, you are asking for an implementation of the "GroupGenerator".
Without any real experience with CastleProxy my recommendation would be to use GetMethods() to get the initial methods listed in the interface and then create a new type on the fly using Reflection.Emit with the new methods that enumerate through the objects and call each corresponding method. The performance shouldn't be too bad.