Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I write a generic container class that implements a given interface in C#?

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?

like image 807
Ron Klein Avatar asked May 11 '09 12:05

Ron Klein


People also ask

Can a generic class implement an interface?

Only generic classes can implement generic interfaces. Normal classes can't implement generic interfaces.

How do you create a generic interface?

You can declare variant generic interfaces by using the in and out keywords for generic type parameters. ref , in , and out parameters in C# cannot be variant. Value types also do not support variance. You can declare a generic type parameter covariant by using the out keyword.

CAN interfaces have generics?

Generic Interfaces in Java are the interfaces that deal with abstract data types. Interface help in the independent manipulation of java collections from representation details. They are used to achieving multiple inheritance in java forming hierarchies. They differ from the java class.

What is the syntax used to declare generic interface?

The general syntax to declare a generic interface is as follows: interface interface-name<T> { void method-name(T t); // public abstract method. } In the above syntax, <T> is called a generic type parameter that specifies any data type used in the interface.


1 Answers

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         }     } } 
like image 192
Marc Gravell Avatar answered Sep 17 '22 06:09

Marc Gravell