Delen via


Multimethods in C# revisited - MultimethodFactory.cs

/// <summary>

/// Generates multimethods corresponding to the specified method signature.

/// </summary>

/// <typeparam name="T">Type of delegate specifying method signature</typeparam>

public sealed class MultimethodFactory<T> where T : class

{

    private static class Constants

    {

        internal const string InvokeMethodName = "Invoke";

        internal const string SpecificInvokeMethodName = "__Invoke";

        internal const string CreateDelegateMethodName = "CreateDelegate";

    }

    private sealed class FieldData

    {

        private readonly FieldBuilder _fieldBuilder;

        private readonly object _value;

        internal FieldBuilder FieldBuilder

        {

            get

            {

                return _fieldBuilder;

    }

        }

        internal object Value

        {

            get

            {

                return _value;

            }

        }

        internal FieldData(FieldBuilder fieldBuilder, object value)

        {

            Debug.Assert(null != fieldBuilder);

            Debug.Assert(null != value);

            _fieldBuilder = fieldBuilder;

            _value = value;

        }

    }

    private static readonly InvocationData _invocationData;

    private readonly List<object> _delegates = new List<object>();

    /// <summary>

    /// Type initializer.

    /// </summary>

    static MultimethodFactory()

    {

        if (!typeof(T).IsPublic || !InvocationData.TryCreate(typeof(T), out _invocationData))

            throw new ArgumentException(string.Format(Properties.Resources.ArgumentExceptionMustBeValidPublicDelegateType, typeof(T).FullName), "T");

    }

    /// <summary>

    /// Creates a new multimethod factory instance.

    /// </summary>

    public MultimethodFactory()

    {

    }

    /// <summary>

    /// Implements a runtime overload of the multimethod.

    /// </summary>

    /// <typeparam name="U">Type of delegate specifying method signature</typeparam>

    /// <param name="delegate">Delegate</param>

    public void Implement<U>(U @delegate) where U : class

    {

        InvocationData invocationData;

        if (!typeof(U).IsPublic || !InvocationData.TryCreate(typeof(U), out invocationData))

            throw new ArgumentException(string.Format(Properties.Resources.ArgumentExceptionMustBeValidPublicDelegateType, typeof(U).FullName), "U");

        if (null == @delegate)

            throw new ArgumentNullException(Properties.Resources.ArgumentNullExceptionDelegate, "delegate");

        Debug.Assert(typeof(U).Equals(@delegate.GetType()));

        if (!_invocationData.IsCompatibleWith(invocationData))

            throw new ArgumentException(string.Format(Properties.Resources.ArgumentExceptionDelegateTypeIsIncompatible, typeof(U).FullName, typeof(T).FullName), "U");

        _delegates.Add(@delegate);

    }

    /// <summary>

    /// Creates a multimethod instance.

    /// </summary>

    /// <returns>Multimethod delegate</returns>

    public T CreateMultimethod()

    {

        return CreateMultimethod(null);

    }

    /// <summary>

    /// Creates a multimethod instance.

    /// </summary>

    /// <param name="debugInfo">Debug configuration</param>

    /// <returns>Multimethod delegate</returns>

    public T CreateMultimethod(DebugInfo debugInfo)

    {

        if (_delegates.Count < 1)

            throw new InvalidOperationException(Properties.Resources.InvalidOperationExceptionNoOverloadsDefined);

        AssemblyBuilder assemblyBuilder = CreateAssemblyBuilder(debugInfo);

        ModuleBuilder moduleBuilder = CreateModuleBuilder(debugInfo, assemblyBuilder);

        FieldData[] fields;

        Type holderType = CreateHolderClass(debugInfo, moduleBuilder, _delegates.ToArray(), _invocationData, out fields);

        SaveArtifacts(debugInfo, assemblyBuilder, moduleBuilder);

        object holder = CreateHolderInstance(holderType, fields);

        MethodInfo createDelegateMethod = holderType.GetMethod(Constants.CreateDelegateMethodName, BindingFlags.Public | BindingFlags.Instance, null, Type.EmptyTypes, null);

      if (null == createDelegateMethod)

            throw new InvalidOperationException(Properties.Resources.InvalidOperationExceptionBadCodeGeneration);

        return (T)createDelegateMethod.Invoke(holder, null);

    }

    private static void SaveArtifacts(DebugInfo debugInfo, AssemblyBuilder assemblyBuilder, ModuleBuilder moduleBuilder)

    {

        Debug.Assert(null != assemblyBuilder);

        Debug.Assert(null != moduleBuilder);

        if (null == debugInfo)

            return;

       // Copy the assembly to the output path.

        string fileName = Path.GetFileName(debugInfo.AssemblyPath);

        assemblyBuilder.Save(fileName);

        File.Move(fileName, debugInfo.AssemblyPath);

        // Copy the module to the output path.

     File.Move(moduleBuilder.FullyQualifiedName, debugInfo.ModulePath);

        // Copy the source to the output path.

        string symbolSourcePath = Path.ChangeExtension(moduleBuilder.FullyQualifiedName, ".pdb");

        string symbolTargetPath = Path.ChangeExtension(debugInfo.ModulePath, ".pdb");

        File.Move(symbolSourcePath, symbolTargetPath);

    }

    private static AssemblyBuilder CreateAssemblyBuilder(DebugInfo debugInfo)

    {

        AssemblyName assemblyName = new AssemblyName(null == debugInfo ? "ANONYMOUS" : Path.GetFileName(debugInfo.AssemblyPath));

        ConstructorInfo attributeConstructor = typeof(SecurityTransparentAttribute).GetConstructor(Type.EmptyTypes);

        if (null == attributeConstructor)

            throw new InvalidOperationException(Properties.Resources.InvalidOperationExceptionBadCodeGeneration);

        AssemblyBuilder assemblyBuilder = AppDomain.CurrentDomain.DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.RunAndSave, new CustomAttributeBuilder[] { new CustomAttributeBuilder(attributeConstructor, new object[0]) });

        attributeConstructor = typeof(DebuggableAttribute).GetConstructor(new Type[] { typeof(DebuggableAttribute.DebuggingModes) });

        assemblyBuilder.SetCustomAttribute(new CustomAttributeBuilder(attributeConstructor, new object[] { DebuggableAttribute.DebuggingModes.DisableOptimizations | DebuggableAttribute.DebuggingModes.Default }));

        return assemblyBuilder;

    }

    private static ModuleBuilder CreateModuleBuilder(DebugInfo debugInfo, AssemblyBuilder assemblyBuilder)

    {

        Debug.Assert(null != assemblyBuilder);

        if (null == debugInfo)

            return assemblyBuilder.DefineDynamicModule("ANONYMOUS");

        return assemblyBuilder.DefineDynamicModule(debugInfo.ModulePath, Path.GetFileName(debugInfo.ModulePath), true);

    }

    private static Type CreateHolderClass(DebugInfo debugInfo, ModuleBuilder moduleBuilder, object[] delegates, InvocationData invocationData, out FieldData[] fields)

    {

     Debug.Assert(null != moduleBuilder);

        Debug.Assert(null != delegates && delegates.Length > 0);

        Debug.Assert(null != invocationData);

        bool isDebuggable = null != debugInfo;

        string sourceFileName = isDebuggable ? debugInfo.SourcePath : null;

        using (SourceWriter sourceWriter = new SourceWriter(isDebuggable, sourceFileName, moduleBuilder))

        {

            TypeBuilder typeBuilder = moduleBuilder.DefineType(ILHelpers.GenerateUniqueName("MultimethodHolderClass"), TypeAttributes.Public | TypeAttributes.Class);

            fields = new FieldData[delegates.Length];

            for (int i = 0; i < fields.Length; ++i)

            {

                // Create runtime overload of "__Invoke" and save the field name for later use.

                object @delegate = delegates[i];

                FieldBuilder fieldBuilder = GenerateSpecificInvokeMethod(sourceWriter, typeBuilder, @delegate.GetType());

                fields[i] = new FieldData(fieldBuilder, @delegate);

            }

            GenerateConstructor(sourceWriter, typeBuilder, fields);

            // Generate public "Invoke" method.

            MethodBuilder publicInvokeMethod = GenerateInvokeMethod(sourceWriter, typeBuilder, invocationData);

            // Create delegate creator method.

            GenerateCreateDelegateMethod(sourceWriter, typeBuilder, typeof(T), publicInvokeMethod);

         // Instantiate generated multimethod holder class.

            return typeBuilder.CreateType();

        }

    }

    private static object CreateHolderInstance(Type holderType, FieldData[] fields)

    {

        Debug.Assert(null != holderType);

        Debug.Assert(null != fields && fields.Length > 0);

        object[] args = new object[fields.Length];

        for (int i = 0; i < args.Length; ++i)

            args[i] = fields[i].Value;

        return Activator.CreateInstance(holderType, args);

  }

    private static void GenerateConstructor(SourceWriter sourceWriter, TypeBuilder typeBuilder, FieldData[] fields)

    {

        Debug.Assert(null != sourceWriter);

        Debug.Assert(null != typeBuilder);

        Debug.Assert(null != fields && fields.Length > 0);

        // Create array of delegate types for constructor.

        Type[] parameterTypes = new Type[fields.Length];

        for (int i = 0; i < parameterTypes.Length; ++i)

            parameterTypes[i] = fields[i].Value.GetType();

        // Default constructor for System.Object.

        ConstructorInfo objectConstructor = typeof(object).GetConstructor(Type.EmptyTypes);

        if (null == objectConstructor)

            throw new InvalidOperationException(Properties.Resources.InvalidOperationExceptionBadCodeGeneration);

        ConstructorBuilder constructorBuilder = typeBuilder.DefineConstructor(MethodAttributes.Public, CallingConventions.Standard, parameterTypes);

        ILGenerator generator = constructorBuilder.GetILGenerator();

        sourceWriter.EmitFunctionHeader(".ctor");

        sourceWriter.EmitSourceLine(generator, "PUSH this");

        generator.Emit(OpCodes.Ldarg_0); // Push "this" onto stack.

        sourceWriter.EmitSourceLine(generator, "CALL System.Object..ctor");

        generator.Emit(OpCodes.Call, objectConstructor); // Invoke System.Object..ctor.

        for (int i = 1; i <= fields.Length; ++i)

        {

            sourceWriter.EmitSourceLine(generator, "PUSH this");

            generator.Emit(OpCodes.Ldarg_0); // Push "this" onto stack.

            sourceWriter.EmitSourceLine(generator, "PUSH ARG {0}", i);

            ILHelpers.EmitLdarg(generator, i);

            sourceWriter.EmitSourceLine(generator, "STORE FIELD {0}", fields[i - 1].FieldBuilder.Name);

            generator.Emit(OpCodes.Stfld, fields[i - 1].FieldBuilder); // Store in field.

        }

        sourceWriter.EmitSourceLine(generator, "RETURN");

        generator.Emit(OpCodes.Ret);

    }

    private static void GenerateCreateDelegateMethod(SourceWriter sourceWriter, TypeBuilder typeBuilder, Type delegateType, MethodBuilder publicInvokeMethod)

    {

        Debug.Assert(null != sourceWriter);

        Debug.Assert(null != typeBuilder);

        Debug.Assert(null != delegateType && typeof(Delegate).IsAssignableFrom(delegateType));

        Debug.Assert(null != publicInvokeMethod);

        // Get delegate's constructor.

        ConstructorInfo delegateConstructor = delegateType.GetConstructor(new Type[] { typeof(object), typeof(IntPtr) });

        if (null == delegateConstructor)

            throw new InvalidOperationException(Properties.Resources.InvalidOperationExceptionBadCodeGeneration);

        // Generate delegate factory method.

        MethodBuilder methodBuilder = typeBuilder.DefineMethod(Constants.CreateDelegateMethodName, MethodAttributes.Public, delegateType, Type.EmptyTypes);

        ILGenerator generator = methodBuilder.GetILGenerator();

        sourceWriter.EmitFunctionHeader(Constants.CreateDelegateMethodName);

        sourceWriter.EmitSourceLine(generator, "DECLARE LOCAL");

        generator.DeclareLocal(delegateType);

        sourceWriter.EmitSourceLine(generator, "PUSH this");

        generator.Emit(OpCodes.Ldarg_0); // Push "this" onto stack.

        sourceWriter.EmitSourceLine(generator, "PUSH {0}", publicInvokeMethod.Name);

        generator.Emit(OpCodes.Ldftn, publicInvokeMethod); // Push reference to "__Invoke" method onto stack.

        sourceWriter.EmitSourceLine(generator, "CREATE {0}", delegateType.FullName);

        generator.Emit(OpCodes.Newobj, delegateConstructor); // Create instance of delegate.

        sourceWriter.EmitSourceLine(generator, "STORE LOCAL");

        generator.Emit(OpCodes.Stloc_0); // Store to local variable.

        sourceWriter.EmitSourceLine(generator, "PUSH LOCAL");

        generator.Emit(OpCodes.Ldloc_0); // Push local variable onto stack to return it.

        sourceWriter.EmitSourceLine(generator, "RETURN");

        generator.Emit(OpCodes.Ret); // Return;

    }

    private static FieldBuilder GenerateSpecificInvokeMethod(SourceWriter sourceWriter, TypeBuilder typeBuilder, Type delegateType)

    {

        Debug.Assert(null != typeBuilder);

        Debug.Assert(null != sourceWriter);

     Debug.Assert(null != delegateType && typeof(Delegate).IsAssignableFrom(delegateType));

        InvocationData invocationData;

        if (!InvocationData.TryCreate(delegateType, out invocationData))

            throw new InvalidOperationException(Properties.Resources.InvalidOperationExceptionBadCodeGeneration);

        // Generate a random name for the field.

        string fieldName = ILHelpers.GenerateUniqueName("InvokerDelegate");

        // Create the private delegate field.

        FieldBuilder fieldBuilder = typeBuilder.DefineField(fieldName, delegateType, FieldAttributes.Private | FieldAttributes.InitOnly);

        // Generate the "__Invoke" method.

        MethodBuilder methodBuilder = typeBuilder.DefineMethod(

            Constants.SpecificInvokeMethodName,

            MethodAttributes.Public,

            invocationData.InvokeMethod.ReturnType,

            invocationData.GetParameterTypes());

        ILGenerator generator = methodBuilder.GetILGenerator();

      sourceWriter.EmitFunctionHeader(string.Format(CultureInfo.InvariantCulture, "{0}[{1}]", Constants.SpecificInvokeMethodName, delegateType.FullName));

        sourceWriter.EmitSourceLine(generator, "PUSH DELEGATE ONTO STACK");

        generator.Emit(OpCodes.Ldarg_0); // Push "this".

        generator.Emit(OpCodes.Ldfld, fieldBuilder); // Pop this and push field.

        sourceWriter.EmitSourceLine(generator, "LOAD ARGS");

        for (int i = 0; i < invocationData.ParameterCount; ++i)

            ILHelpers.EmitLdarg(generator, i + 1);

        sourceWriter.EmitSourceLine(generator, "INVOKE DELEGATE");

        generator.EmitCall(OpCodes.Callvirt, invocationData.InvokeMethod, null); // Call the "Invoke" method.

        sourceWriter.EmitSourceLine(generator, "RETURN");

        generator.Emit(OpCodes.Ret); // Return.

        Debug.Assert(0 == string.Compare(fieldName, fieldBuilder.Name, StringComparison.Ordinal));

        return fieldBuilder;

    }

    private static MethodBuilder GenerateInvokeMethod(SourceWriter sourceWriter, TypeBuilder typeBuilder, InvocationData invocationData)

    {

        Debug.Assert(null != sourceWriter);

        Debug.Assert(null != typeBuilder);

        Debug.Assert(null != invocationData);

        // Create method.

        MethodBuilder methodBuilder = typeBuilder.DefineMethod(

            Constants.InvokeMethodName,

            MethodAttributes.Private,

            invocationData.InvokeMethod.ReturnType,

            invocationData.GetParameterTypes());

        ILGenerator generator = methodBuilder.GetILGenerator();

        sourceWriter.EmitFunctionHeader(Constants.InvokeMethodName);

        // Declare local object array.

        sourceWriter.EmitSourceLine(generator, "DECLARE LOCAL");

        generator.DeclareLocal(typeof(object[]));

        // Create an array of three objects and assign it to the local variable.

        sourceWriter.EmitSourceLine(generator, "CREATE LOCAL");

        ILHelpers.EmitLdc_I4(generator, invocationData.ParameterCount);

        generator.Emit(OpCodes.Newarr, typeof(object)); // Create array of objects of size given by value on top of stack (i.e. 3) and push result onto stack.

        generator.Emit(OpCodes.Stloc_0); // Store the result (i.e. the newly created array) in the local variable.

        // Marshal in- and ref-parameters.

        for (int i = 0; i < invocationData.ParameterCount; ++i)

        {

            ParameterData parameter = invocationData.GetParameter(i);

            switch (parameter.Kind)

            {

                case ParameterKind.In:

                    sourceWriter.EmitSourceLine(generator, "MARSHAL ARG {0} [{1}]", i, parameter.Kind);

                    generator.Emit(OpCodes.Ldloc_0); // Push local array variable onto stack.

                   ILHelpers.EmitLdc_I4(generator, i); // Push array index onto stack.

                    ILHelpers.EmitLdarg(generator, i + 1); // Push corresponding argument's value onto stack.

                    generator.Emit(OpCodes.Stelem_Ref);

                    break;

                case ParameterKind.Out:

                    break;

                case ParameterKind.Ref:

                    sourceWriter.EmitSourceLine(generator, "MARSHAL ARG {0} [{1}]", i, parameter.Kind);

                    generator.Emit(OpCodes.Ldloc_0); // Push local array variable onto stack.

                    ILHelpers.EmitLdc_I4(generator, i); // Push array index onto stack.

                    ILHelpers.EmitLdarg(generator, i + 1); // Push corresponding argument's value or address onto stack.

                    generator.Emit(OpCodes.Ldind_Ref); // Fetch the value of by-reference values.

                    generator.Emit(OpCodes.Stelem_Ref);

                    break;

                default:

                    Debug.Fail("Unexpected");

                    throw new InvalidOperationException(Properties.Resources.InvalidOperationExceptionBadCodeGeneration);

            }

        }

        // Invoke this.GetType(). This leaves the Type instance on the stack.

        sourceWriter.EmitSourceLine(generator, "CALL GetType");

        generator.Emit(OpCodes.Ldarg_0); // Push "this" onto stack.

        MethodInfo getTypeMethod = typeof(object).GetMethod("GetType");

        if (null == getTypeMethod)

            throw new InvalidOperationException(Properties.Resources.InvalidOperationExceptionBadCodeGeneration);

        generator.EmitCall(OpCodes.Call, getTypeMethod, null);

        // Invoke this.GetType().InvokeMember(Constants.PrivateInvokeMethodName, BindingFlags, null, target, args). This leaves the return value on the stack.

        sourceWriter.EmitSourceLine(generator, "CALL InvokeMember");

        generator.Emit(OpCodes.Ldstr, Constants.SpecificInvokeMethodName);

        generator.Emit(OpCodes.Ldc_I4, (int)(BindingFlags.Instance | BindingFlags.Public | BindingFlags.InvokeMethod));

        generator.Emit(OpCodes.Ldnull);

        generator.Emit(OpCodes.Ldarg_0); // Push "this" onto stack.

        generator.Emit(OpCodes.Ldloc_0); // Push local array variable onto stack.

        MethodInfo invokeMemberMethod = typeof(Type).GetMethod("InvokeMember", BindingFlags.Instance | BindingFlags.Public, null, new Type[] { typeof(string), typeof(BindingFlags), typeof(Binder), typeof(object), typeof(object[]) }, null);

        if (null == invokeMemberMethod)

            throw new InvalidOperationException(Properties.Resources.InvalidOperationExceptionBadCodeGeneration);

        generator.EmitCall(OpCodes.Callvirt, invokeMemberMethod, null);

        // Unmarshal ref- and out-parameters.

        for (int i = 0; i < invocationData.ParameterCount; ++i)

        {

            ParameterData parameter = invocationData.GetParameter(i);

            switch (parameter.Kind)

            {

                case ParameterKind.In:

                    break;

                case ParameterKind.Out:

                case ParameterKind.Ref:

                    sourceWriter.EmitSourceLine(generator, "UNMARSHAL ARG {0} [{1}]", i, parameter.Kind);

                    ILHelpers.EmitLdarg(generator, i + 1); // Push argument 2's address onto stack.

                    generator.Emit(OpCodes.Ldloc_0); // Push local array variable onto stack.

                    ILHelpers.EmitLdc_I4(generator, i); // Push array index 1 onto stack.

                    generator.Emit(OpCodes.Ldelem_Ref); // Load element from array and pop arguments off stack.

                    generator.Emit(OpCodes.Stind_Ref); // Store elemtn to argument 2.

                    break;

                default:

                    Debug.Fail("Unexpected");

                    throw new InvalidOperationException(Properties.Resources.InvalidOperationExceptionBadCodeGeneration);

            }

        }

        // Pop return value off stack.

        if (typeof(void) == invocationData.InvokeMethod.ReturnType)

            generator.Emit(OpCodes.Pop);

        // Return.

        sourceWriter.EmitSourceLine(generator, "RETURN");

        generator.Emit(OpCodes.Ret);

        return methodBuilder;

    }

}

 

MultimethodFactory.cs

Comments

  • Anonymous
    November 11, 2008
    The comment has been removed