Implementing a anonymous interface in c#

Some time ago a read a blog post about implementing anonyme classes and interfaces in java. In c# it is possible to generate anonyme classes but it is not possible to implement anonyme interfaces out of the box.

var anonymePerson = new { Name = "Mathias", Age = 35,
Calculate = new System.Action(() => { Console.WriteLine("Caculate"); })  };

With a little help of reflection and code emiting it is possible to implement this feature. To demonstrate this i have implementet the AnonymousInterface class. This class has an Create method to generate an type that maps an interface and an anonymous type. To demonstrate the use of the AnonymousInterface class i implemented the IFoo Interface and an Unit Test.

public interface IFoo
{
  string Name { get; set; }
  int Age { get; set; }
  Func<int, int> Calculate { get; set; }
}

This unit test checks if the creation of a class that implements the IFoo interface works correct.

private const string name = "Mathias";
private const int age = 34;
private const int value = 5;

[TestMethod]
public void AnonymeInterfaceCreateTest()
{
  Action action = new Action(() => { });
  IFoo foo = AnonymeInterface.Create<IFoo>(new
  {
    Name = name,
    Age = age,
    Calculate = new System.Func<int, int>( x => x * x )
  });
  Assert.IsTrue(name == foo.Name);
  Assert.IsTrue(age == foo.Age);
  Assert.IsTrue((value * value) == foo.Calculate(value));
}

The AnonymeInterface class has only one public method, the create method. With this generic method it is possible to create the type.

public static T Create<T>(object o)
{
  Type type = EmitInterfaceType<T>(o);
  object createdType = Activator.CreateInstance(type);
  FillProperties(o, createdType);
  return (T)createdType;
}

The static create methode uses the EmitInterfaceType and the FillProperties methodes to create the type. The EmitInterfaceType methode creates a concrete type out of the anonymous class and the interterface. The FillProperies methode fills the properties of the concrete type with the properties of the anonymous type.

 

private static Type EmitInterfaceType<T>(object o)
{
    PropertyInfo[] interfacePropertyInfos = typeof(T).GetProperties();
    PropertyInfo[] anonymerTypePropertyInfo = o.GetType().GetProperties();

    foreach (var property in interfacePropertyInfos)
    {
        if (anonymerTypePropertyInfo.Where(
            anonymerTypeProperty => anonymerTypeProperty.Name == property.Name).Count() == 0)
        {
            throw new Exception("Interface und anonymer Type passen nicht zusammen");
        }
    }

    string implementationTypeName = typeof(T).Name + "Impl";
    AssemblyName asseblyName = new AssemblyName("DynamicAssembly");
    AssemblyBuilder assemblyBuilder = AppDomain.CurrentDomain.
        DefineDynamicAssembly(asseblyName, AssemblyBuilderAccess.RunAndSave);
    var moduleBuilder = assemblyBuilder.DefineDynamicModule(asseblyName.Name, true);
    var typeBuilder = moduleBuilder.DefineType(implementationTypeName,
        TypeAttributes.Public | TypeAttributes.BeforeFieldInit | TypeAttributes.Serializable |
        TypeAttributes.Sealed, typeof(object) //, new Type[] { typeof(IFoo) }
        );
    typeBuilder.AddInterfaceImplementation(typeof(T));

    foreach (var property in interfacePropertyInfos)
    {
        string fieldName = property.Name + "_";
        FieldBuilder nameFieldImplementation = typeBuilder.DefineField(fieldName,
                                                typeof(string),
                                                FieldAttributes.Private);

        var namePropertyImplentation = typeBuilder.DefineProperty(property.Name,
            PropertyAttributes.None, property.PropertyType, null);

        MethodAttributes getSetAttr =
        MethodAttributes.Public | MethodAttributes.ReuseSlot | MethodAttributes.Virtual |
            MethodAttributes.HideBySig;

        string getPropertyMethodName = "get_" + property.Name;
        MethodBuilder nameGetPropertyMethodeBuilder =
        typeBuilder.DefineMethod(getPropertyMethodName,
                                    getSetAttr,
                                    property.PropertyType,
                                    Type.EmptyTypes);

        string setPropertyMethodName = "set_" + property.Name;
        MethodBuilder nameSetPropertyMethodeBuilder =
        typeBuilder.DefineMethod(setPropertyMethodName,
                                    getSetAttr,
                                    null,
                                    new Type[] { property.PropertyType });

        ILGenerator nameGetIL = nameGetPropertyMethodeBuilder.GetILGenerator();

        nameGetIL.Emit(OpCodes.Ldarg_0);
        nameGetIL.Emit(OpCodes.Ldfld, nameFieldImplementation);
        nameGetIL.Emit(OpCodes.Ret);

        ILGenerator nameSetIL = nameSetPropertyMethodeBuilder.GetILGenerator();

        nameSetIL.Emit(OpCodes.Ldarg_0);
        nameSetIL.Emit(OpCodes.Ldarg_1);
        nameSetIL.Emit(OpCodes.Stfld, nameFieldImplementation);
        nameSetIL.Emit(OpCodes.Ret);

        namePropertyImplentation.SetGetMethod(nameGetPropertyMethodeBuilder);
        namePropertyImplentation.SetSetMethod(nameSetPropertyMethodeBuilder);

        typeBuilder.DefineMethodOverride(nameGetPropertyMethodeBuilder,
            typeof(T).GetMethod(getPropertyMethodName));
        typeBuilder.DefineMethodOverride(nameSetPropertyMethodeBuilder,
            typeof(T).GetMethod(setPropertyMethodName));
    }

    var type = typeBuilder.CreateType();
    return type;
}
private static void FillProperties(object o, object createdType)
{
    PropertyInfo[] concretePropertyInfos = createdType.GetType().GetProperties();
    PropertyInfo[] anonymerTypePropertyInfo = o.GetType().GetProperties();

    foreach (var anonymeProperty in anonymerTypePropertyInfo)
    {
        concretePropertyInfos.Where(
            concreteProperty => concreteProperty.Name == anonymeProperty.Name).ToList().First().
            SetValue(createdType, anonymeProperty.GetValue(o, null), null);
    }
}

The whole solution can be downloaded from my skydrive account.

Advertisements


If you have a note or a question please write a comment.

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s