Skip to main content
Tweeted twitter.com/StackCodeReview/status/1104623062534758400
deleted 1 character in body
Source Link
Dmitry Nogin
  • 6.1k
  • 3
  • 20
  • 40

FacadeMixin type emitter (Well, not sure that implementation really looks readable - I hardly understand IL. But it works for method delegation :)

Facade type emitter (Well, not sure that implementation really looks readable - I hardly understand IL. But it works for method delegation :)

Mixin type emitter (Well, not sure that implementation really looks readable - I hardly understand IL. But it works for method delegation :)

Source Link
Dmitry Nogin
  • 6.1k
  • 3
  • 20
  • 40

Mixin implementation helper

While implementing things like repositories we often can see that reading and writing parts can be done totally independently. The same time client side will win from the single reference (who wants to inject two parameters instead of one in many places). What do you think about a little bit of IL magic?

Let's say we have:

interface IDocumentReader
{
   Document ReadDocument(int id);
   IEnumerable<Document> ReadDocuments(DateTime date);
}  

interface IDocumentWriter
{
   void Write(Document document);
} 

Now magic:

[Mixin]
interface IDocumentRepository : IDocumentReader, IDocumentWriter
{
}

I configured my IoC container to generate and register implementations of mixin interfaces like:

class RTYRUYTRUYTRTYU23576475623 : IDocumentRepository 
{
   public RTYRUYTRUYTRTYU23576475623(IDocumentReader a1, IDocumentWriter a2)    
   {
     _a1 = a1;
     _a2 = a2;
   }

   Document ReadDocument(int id) => _a1.ReadDocument(id);
   IEnumerable<Document> ReadDocuments(DateTime date) => _a1.ReadDocuments(date);
   void Write(Document document) => _a2.Write(document);
}    

Now I can inject just IDocumentRepository.

Here comes library code. Attribute:

[AttributeUsage(AttributeTargets.Interface, AllowMultiple = false, Inherited = false)]
public class MixinAttribute : Attribute
{
}

Facade class we use to help IoC container to emit realization types for mixin interface registration:

public class Mixin
{
    readonly static ConcurrentDictionary<Type, Type> _map = 
        new ConcurrentDictionary<Type, Type>();

    public static Type Emit(Type mixinInterface)
    {
        Contract.Requires<ArgumentNullException>(mixinInterface != null);

        return _map.GetOrAdd(
            mixinInterface, 
            mi => new MixinFactory(mi).Emit());
    }

    public static object Create(Type mixinInterface, params object[] args)
    {
        Contract.Requires<ArgumentNullException>(mixinInterface != null);
        Contract.Requires<ArgumentNullException>(args != null);

        return Activator.CreateInstance(Emit(mixinInterface), args);
    }

    public static T Create<T>(params object[] args)
    {
        Contract.Requires<ArgumentNullException>(args != null);

        return (T)Create(typeof(T), args);
    }
}

Facade type emitter (Well, not sure that implementation really looks readable - I hardly understand IL. But it works for method delegation :)

class MixinFactory
{
    public MixinFactory(Type mixinInterface)
    {
        Contract.Requires<ArgumentNullException>(mixinInterface != null);
        Contract.Requires<ArgumentException>(mixinInterface.IsInterface);

        MixinInterface = mixinInterface;
    }

    public Type Emit()
    {
        Contract.Ensures(Contract.Result<Type>() != null);

        var tb = TypeBuilder();
        var fbs = FieldBuilders(tb);
        DefineConstructor(tb, fbs);
        foreach (var fb in fbs)
            DelegateTo(tb, fb);

        return tb.CreateType();
    }

    Type MixinInterface { get; }

    IEnumerable<Type> MixedInterfaces => MixinInterface.GetInterfaces();

    void DefineConstructor(TypeBuilder tb, IEnumerable<FieldBuilder> fbs)
    {
        Contract.Requires<ArgumentNullException>(tb != null);
        Contract.Requires<ArgumentNullException>(fbs != null);

        ConstructorBuilder ctor = ConstructorBuilder(tb, fbs);
        ILGenerator il = ctor.GetILGenerator();

        // Call base constructor
        ConstructorInfo ci = tb.BaseType.GetConstructor(new Type[] { });
        il.Emit(OpCodes.Ldarg_0);
        il.Emit(OpCodes.Call, typeof(object).GetConstructor(new Type[0]));

        // Store type parameters in private fields
        for (ushort i = 0; i < fbs.Count(); i++)
        {
            il.Emit(OpCodes.Ldarg_0);
            il.Emit(OpCodes.Ldarg, i + 1);
            il.Emit(OpCodes.Stfld, fbs.ElementAt(i));
        }
        il.Emit(OpCodes.Ret);
    }

    void DelegateTo(TypeBuilder tb, FieldInfo fi)
    {
        Contract.Requires<ArgumentNullException>(tb != null);
        Contract.Requires<ArgumentNullException>(fi != null);

        foreach (var mi in fi.FieldType.GetMethods())
        {
            var mb = tb.DefineMethod(
                mi.Name,
                mi.Attributes & (~MethodAttributes.Abstract), // Could not call absract method, so remove flag
                mi.ReturnType,
                mi.GetParameters().Select(p => p.ParameterType).ToArray());

            if (mi.IsGenericMethod)
            {
                var gas = mi
                    .GetGenericArguments();

                var gtpbs = mb.DefineGenericParameters(gas
                    .Select(t => t.Name)
                    .ToArray());

                for (int i = 0; i < gas.Length; i++)
                {
                    var ga = gas[i];
                    var gtpb = gtpbs[i];
                    gtpb.SetGenericParameterAttributes(ga.GenericParameterAttributes);                        
                    gtpb.SetInterfaceConstraints(ga.GetGenericParameterConstraints());                        
                }
            }       
                
            mb.SetReturnType(mi.ReturnType);                

            // Emit method body
            ILGenerator il = mb.GetILGenerator();
            il.Emit(OpCodes.Ldarg_0);
            il.Emit(OpCodes.Ldfld, fi);

            // Call with same parameters
            for (int i = 0; i < mi.GetParameters().Length; i++)
                il.Emit(OpCodes.Ldarg, i + 1);

            il.Emit(OpCodes.Callvirt, mi);
            il.Emit(OpCodes.Ret);
        }
    }

    ConstructorBuilder ConstructorBuilder(TypeBuilder tb, IEnumerable<FieldBuilder> fbs) =>
        tb.DefineConstructor(
            MethodAttributes.Public,
            CallingConventions.Standard,
            fbs.Select(fb => fb.FieldType).ToArray());

    IEnumerable<FieldBuilder> FieldBuilders(TypeBuilder tb) => MixedInterfaces
        .Select((mi, i) => tb.DefineField(
            "_i" + i,
            mi,
            FieldAttributes.Private))
        .ToArray();

    TypeBuilder TypeBuilder() => ModuleBuilder()
        .DefineType(
            Guid.NewGuid().ToString(),
            TypeAttributes.Class | TypeAttributes.Public,
            typeof(object),
            new[] { MixinInterface });
    
    ModuleBuilder ModuleBuilder() => AssemblyBuilder()
        .DefineDynamicModule(Guid.NewGuid().ToString());

    AssemblyBuilder AssemblyBuilder() => AppDomain.CurrentDomain.DefineDynamicAssembly(
            new AssemblyName(
                Guid.NewGuid().ToString()),
                AssemblyBuilderAccess.RunAndSave);
}