views:

995

answers:

7

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?

+2  A: 

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
I think that doesn't answer the question.
DonkeyMaster
@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
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
A: 

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
C# 4.0 won't be here for some time. There's not even a CTP out yet!
DonkeyMaster
A: 

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

var startables = new List<IStartable>( array_of_startables );
startables.ForEach( t => t.Start(); }
TcKs
This is the first thing that came to my mind too - but he's asking for an implementation of the "GroupGenerator" class above.
Robert Venables
A: 

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
+11  A: 

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
Saved me some typing :-)
Robert Venables
I think that you have a minor mistake there (won't compile): Instead of: Cache<T>.Type = type.CreateType(); It should be: Type = type.CreateType();
Ron Klein
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
@Ron - re "Type =" - they are identical; I just wanted to avoid ambiguity with System.Type
Marc Gravell
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
Works like a charm, accepted answer!
Ron Klein
+2  A: 

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
+1  A: 

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
Thanks for the tip, I'll look into it when I have the time.
Ron Klein