26

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?

Ron Klein
  • 9,178
  • 9
  • 55
  • 88
  • So you *don't* want to have an StartableGroup class? What's wrong with it? – Noldorin May 11 '09 at 12:14
  • Can I ask: why? What is the problem that this needs to solve? (this may impact the answer...). – Marc Gravell May 11 '09 at 12:20
  • @Noldorin, @Marc Gravell, motivation added to the original question. – Ron Klein May 11 '09 at 12:53
  • Re your comment on arguments - it is done easily enough, but I'd probably have to unroll the "foreach" into the IL. Which is a bit of reflector work. I can probably fill in the blanks if you need, but not right at this second (busy for an hour or so). Let me know if you'd value this. – Marc Gravell May 11 '09 at 19:09
  • Updated to implement args; note that it doesn't have a try/finally for the dispose yet - will add later. – Marc Gravell May 12 '09 at 04:32
  • Added the try/finally, and a bit of error-handling (i.e. it knows what to do if you give it something that isn't an interface, or methods with return values). – Marc Gravell May 12 '09 at 06:54
  • what you're describing is a 'composite' design pattern http://en.wikipedia.org/wiki/Composite_pattern It's pretty easy to achieve with DynamicProxy and InterfaceProxyWithTargetInterface – Krzysztof Kozmic Jun 09 '09 at 11:47

7 Answers7

28

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
        }
    }
}
Marc Gravell
  • 1,026,079
  • 266
  • 2,566
  • 2,900
  • I think that you have a minor mistake there (won't compile): Instead of: Cache.Type = type.CreateType(); It should be: Type = type.CreateType(); – Ron Klein May 11 '09 at 15:23
  • I tried the suggested code, and it seems that your answer does not cover methods with arguments (see the constraint "The interface has only void methods, with or without arguments"). Currently there's an exception when the interface includes a method with a single argument. – Ron Klein May 11 '09 at 15:35
  • @Ron - re "Type =" - they are identical; I just wanted to avoid ambiguity with System.Type – Marc Gravell May 11 '09 at 19:08
  • This solution is very close to what I'm looking for. So +1 for the effort so far. I'll be more than happy to accept this as an answer if methods with arguments are covered. Thanks again! – Ron Klein May 11 '09 at 22:58
4

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());
ICR
  • 13,896
  • 4
  • 50
  • 78
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
Brian Ensink
  • 11,092
  • 3
  • 50
  • 63
  • 1
    I think that doesn't answer the question. – DonkeyMaster May 11 '09 at 13:37
  • @DonkeyMaster - No it doesn't answer the exact question but I think its a possible alternative if I undestand the question correctly. My post offers a manually written solution, Marc Gravell's excellent sample offers a (runtime) code generation solution. I don't know of a way off hand to do it without either: the original poster asked for a solution "without manually writing the code, and without code generation". – Brian Ensink May 11 '09 at 17:55
  • Indeed, as @DonkeyMaster noted, this does not answer the question. It makes the code clearer and perhaps more elegant, but the question remains: how can I create such code at runtime, without having to write it (or generate it) at design time? – Ron Klein May 11 '09 at 22:05
2

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.

Maslow
  • 18,464
  • 20
  • 106
  • 193
0

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.

TheSoftwareJedi
  • 34,421
  • 21
  • 109
  • 151
0

You can use the "List" class and their method "ForEach".

var startables = new List<IStartable>( array_of_startables );
startables.ForEach( t => t.Start(); }
TcKs
  • 25,849
  • 11
  • 66
  • 104
0

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.

Robert Venables
  • 5,943
  • 1
  • 23
  • 35