File: System\Reflection\DispatchProxyGenerator.cs
Web Access
Project: src\src\libraries\System.Reflection.DispatchProxy\src\System.Reflection.DispatchProxy.csproj (System.Reflection.DispatchProxy)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Reflection.Emit;
using System.Runtime.CompilerServices;
using System.Runtime.Loader;
using System.Threading;
 
namespace System.Reflection
{
    // Helper class to handle the IL EMIT for the generation of proxies.
    // Much of this code was taken directly from the Silverlight proxy generation.
    // Differences between this and the Silverlight version are:
    //  1. This version is based on DispatchProxy from NET Native and CoreCLR, not RealProxy in Silverlight ServiceModel.
    //     There are several notable differences between them.
    //  2. Both DispatchProxy and RealProxy permit the caller to ask for a proxy specifying a pair of types:
    //     the interface type to implement, and a base type.  But they behave slightly differently:
    //       - RealProxy generates a proxy type that derives from Object and *implements" all the base type's
    //         interfaces plus all the interface type's interfaces.
    //       - DispatchProxy generates a proxy type that *derives* from the base type and implements all
    //         the interface type's interfaces.  This is true for both the CLR version in NET Native and this
    //         version for CoreCLR.
    //  3. DispatchProxy and RealProxy use different type hierarchies for the generated proxies:
    //       - RealProxy type hierarchy is:
    //             proxyType : proxyBaseType : object
    //         Presumably the 'proxyBaseType' in the middle is to allow it to implement the base type's interfaces
    //         explicitly, preventing collision for same name methods on the base and interface types.
    //       - DispatchProxy hierarchy is:
    //             proxyType : baseType (where baseType : DispatchProxy)
    //         The generated DispatchProxy proxy type does not need to generate implementation methods
    //         for the base type's interfaces, because the base type already must have implemented them.
    //  4. RealProxy required a proxy instance to hold a backpointer to the RealProxy instance to mirror
    //     the .NET Remoting design that required the proxy and RealProxy to be separate instances.
    //     But the DispatchProxy design encourages the proxy type to *be* an DispatchProxy.  Therefore,
    //     the proxy's 'this' becomes the equivalent of RealProxy's backpointer to RealProxy, so we were
    //     able to remove an extraneous field and ctor arg from the DispatchProxy proxies.
    //
    internal static class DispatchProxyGenerator
    {
        // Generated proxies have a private MethodInfo[] field that generated methods use to get the corresponding MethodInfo.
        // It is the first field in the class and the first ctor parameter.
        private const int MethodInfosFieldAndCtorParameterIndex = 0;
 
        // We group AssemblyBuilders by the ALC of the base type's assembly.
        // This allows us to granularly unload generated proxy types.
        private static readonly ConditionalWeakTable<AssemblyLoadContext, ProxyAssembly> s_alcProxyAssemblyMap = new();
        private static readonly MethodInfo s_dispatchProxyInvokeMethod = typeof(DispatchProxy).GetMethod("Invoke", BindingFlags.NonPublic | BindingFlags.Instance)!;
        private static readonly MethodInfo s_getTypeFromHandleMethod = typeof(Type).GetMethod("GetTypeFromHandle", new Type[] { typeof(RuntimeTypeHandle) })!;
        private static readonly MethodInfo s_makeGenericMethodMethod = GetGenericMethodMethodInfo();
 
        [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2026:RequiresUnreferencedCode",
            Justification = "MakeGenericMethod is safe here because the user code invoking the generic method will reference " +
            "the GenericTypes being used, which will guarantee the requirements of the generic method.")]
        private static MethodInfo GetGenericMethodMethodInfo() =>
            typeof(MethodInfo).GetMethod("MakeGenericMethod", new Type[] { typeof(Type[]) })!;
 
        // Returns a new instance of a proxy the derives from 'baseType' and implements 'interfaceType'
        [RequiresDynamicCode("Defining a dynamic assembly requires generating code at runtime")]
        internal static object CreateProxyInstance(
            [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] Type baseType,
            [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] Type interfaceType,
            string interfaceParameter, string proxyParameter)
        {
            Debug.Assert(baseType != null);
            Debug.Assert(interfaceType != null);
 
            AssemblyLoadContext? alc = AssemblyLoadContext.GetLoadContext(baseType.Assembly);
            Debug.Assert(alc != null);
 
            ProxyAssembly proxyAssembly = s_alcProxyAssemblyMap.GetValue(alc, static x => new ProxyAssembly(x));
            GeneratedTypeInfo proxiedType = proxyAssembly.GetProxyType(baseType, interfaceType, interfaceParameter, proxyParameter);
            return Activator.CreateInstance(proxiedType.GeneratedType, new object[] { proxiedType.MethodInfos })!;
        }
 
        private sealed class GeneratedTypeInfo
        {
            public GeneratedTypeInfo(
                [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] Type generatedType,
                MethodInfo[] methodInfos)
            {
                GeneratedType = generatedType;
                MethodInfos = methodInfos;
            }
 
            [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)]
            public Type GeneratedType { get; }
            public MethodInfo[] MethodInfos { get; }
        }
 
        private sealed class ProxyAssembly
        {
            // Proxies are requested for a pair of types: base type and interface type.
            // The generated proxy will subclass the given base type and implement the interface type.
            // We maintain a cache keyed by 'base type' containing a dictionary keyed by interface type,
            // containing the generated proxy type for that pair. There are likely to be few (maybe only 1)
            // base type in use for many interface types.
            // Note: this differs from Silverlight's RealProxy implementation which keys strictly off the
            // interface type. But this does not allow the same interface type to be used with more than a
            // single base type. The implementation here permits multiple interface types to be used with
            // multiple base types, and the generated proxy types will be unique.
            // This cache of generated types grows unbounded, one element per unique T/ProxyT pair.
            // This approach is used to prevent regenerating identical proxy types for identical T/Proxy pairs,
            // which would ultimately be a more expensive leak.
            // Proxy instances are not cached. Their lifetime is entirely owned by the caller of DispatchProxy.Create.
            private readonly Dictionary<Type, Dictionary<Type, GeneratedTypeInfo>> _baseTypeAndInterfaceToGeneratedProxyType = new Dictionary<Type, Dictionary<Type, GeneratedTypeInfo>>();
 
            private readonly AssemblyBuilder _ab;
            private readonly ModuleBuilder _mb;
            private int _typeId;
 
            private readonly HashSet<string> _ignoresAccessAssemblyNames = new HashSet<string>();
            private ConstructorInfo? _ignoresAccessChecksToAttributeConstructor;
 
            [RequiresDynamicCode("Defining a dynamic assembly requires generating code at runtime")]
            public ProxyAssembly(AssemblyLoadContext alc)
            {
                string name;
                if (alc == AssemblyLoadContext.Default)
                {
                    name = "ProxyBuilder";
                }
                else
                {
                    string? alcName = alc.Name;
                    name = string.IsNullOrEmpty(alcName) ? $"DispatchProxyTypes.{alc.GetHashCode()}" : $"DispatchProxyTypes.{new AssemblyName { Name = alcName }}";
                }
 
                AssemblyBuilderAccess builderAccess =
                    alc.IsCollectible ? AssemblyBuilderAccess.RunAndCollect : AssemblyBuilderAccess.Run;
                _ab = AssemblyBuilder.DefineDynamicAssembly(new AssemblyName(name), builderAccess);
                _mb = _ab.DefineDynamicModule("testmod");
            }
 
            // Gets or creates the ConstructorInfo for the IgnoresAccessChecksAttribute.
            // This attribute is both defined and referenced in the dynamic assembly to
            // allow access to internal types in other assemblies.
            internal ConstructorInfo IgnoresAccessChecksAttributeConstructor =>
                _ignoresAccessChecksToAttributeConstructor ??= IgnoreAccessChecksToAttributeBuilder.AddToModule(_mb);
 
            public GeneratedTypeInfo GetProxyType(
                [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] Type baseType,
                [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] Type interfaceType,
                string interfaceParameter, string proxyParameter)
            {
                lock (_baseTypeAndInterfaceToGeneratedProxyType)
                {
                    if (!_baseTypeAndInterfaceToGeneratedProxyType.TryGetValue(baseType, out Dictionary<Type, GeneratedTypeInfo>? interfaceToProxy))
                    {
                        interfaceToProxy = new Dictionary<Type, GeneratedTypeInfo>();
                        _baseTypeAndInterfaceToGeneratedProxyType[baseType] = interfaceToProxy;
                    }
 
                    if (!interfaceToProxy.TryGetValue(interfaceType, out GeneratedTypeInfo? generatedProxy))
                    {
                        generatedProxy = GenerateProxyType(baseType, interfaceType, interfaceParameter, proxyParameter);
                        interfaceToProxy[interfaceType] = generatedProxy;
                    }
 
                    return generatedProxy;
                }
            }
 
            // Unconditionally generates a new proxy type derived from 'baseType' and implements 'interfaceType'
            [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2062:UnrecognizedReflectionPattern",
                Justification = "interfaceType is annotated as preserve All members, so any Types returned from GetInterfaces should be preserved as well once https://github.com/mono/linker/issues/1731 is fixed.")]
            private GeneratedTypeInfo GenerateProxyType(
                [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] Type baseType,
                [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] Type interfaceType,
                string interfaceParameter, string proxyParameter)
            {
                // Parameter validation is deferred until the point we need to create the proxy.
                // This prevents unnecessary overhead revalidating cached proxy types.
 
                // The interface type must be an interface, not a class
                if (!interfaceType.IsInterface)
                {
                    throw new ArgumentException(SR.Format(SR.InterfaceType_Must_Be_Interface, interfaceType.FullName), interfaceParameter);
                }
 
                // The base type cannot be sealed because the proxy needs to subclass it.
                if (baseType.IsSealed)
                {
                    throw new ArgumentException(SR.Format(SR.BaseType_Cannot_Be_Sealed, baseType.FullName), proxyParameter);
                }
 
                // The base type cannot be abstract
                if (baseType.IsAbstract)
                {
                    throw new ArgumentException(SR.Format(SR.BaseType_Cannot_Be_Abstract, baseType.FullName), proxyParameter);
                }
 
                // The base type must have a public default ctor
                if (baseType.GetConstructor(Type.EmptyTypes) == null)
                {
                    throw new ArgumentException(SR.Format(SR.BaseType_Must_Have_Default_Ctor, baseType.FullName), proxyParameter);
                }
 
                // Create a type that derives from 'baseType' provided by caller
                ProxyBuilder pb = CreateProxy("generatedProxy", baseType);
 
                foreach (Type t in interfaceType.GetInterfaces())
                    // interfaceType is annotated as preserve All members, so any Types returned from GetInterfaces should be preserved as well once https://github.com/mono/linker/issues/1731 is fixed.
#pragma warning disable IL2072
                    pb.AddInterfaceImpl(t);
#pragma warning restore IL2072
 
                pb.AddInterfaceImpl(interfaceType);
 
                GeneratedTypeInfo generatedProxyType = pb.CreateType();
                return generatedProxyType;
            }
 
            [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2067:UnrecognizedReflectionPattern",
                Justification = "Only the parameterless ctor is referenced on proxyBaseType. Other members can be trimmed if unused.")]
            private ProxyBuilder CreateProxy(
                string name,
                [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] Type proxyBaseType)
            {
                int nextId = Interlocked.Increment(ref _typeId);
                TypeBuilder tb = _mb.DefineType($"{name}_{nextId}", TypeAttributes.Public, proxyBaseType);
                return new ProxyBuilder(this, tb, proxyBaseType);
            }
 
            // Generates an instance of the IgnoresAccessChecksToAttribute to
            // identify the given assembly as one which contains internal types
            // the dynamic assembly will need to reference.
            internal void GenerateInstanceOfIgnoresAccessChecksToAttribute(string assemblyName)
            {
                // Add this assembly level attribute:
                // [assembly: System.Runtime.CompilerServices.IgnoresAccessChecksToAttribute(assemblyName)]
                ConstructorInfo attributeConstructor = IgnoresAccessChecksAttributeConstructor;
                CustomAttributeBuilder customAttributeBuilder =
                    new CustomAttributeBuilder(attributeConstructor, new object[] { assemblyName });
                _ab.SetCustomAttribute(customAttributeBuilder);
            }
 
            // Ensures the type we will reference from the dynamic assembly
            // is visible.  Non-public types need to emit an attribute that
            // allows access from the dynamic assembly.
            internal void EnsureTypeIsVisible(Type type)
            {
                if (!type.IsVisible)
                {
                    string assemblyName = type.Assembly.GetName().Name!;
                    if (_ignoresAccessAssemblyNames.Add(assemblyName))
                    {
                        GenerateInstanceOfIgnoresAccessChecksToAttribute(assemblyName);
                    }
                }
            }
        }
 
        private sealed class ProxyBuilder
        {
            private readonly ProxyAssembly _assembly;
            private readonly TypeBuilder _tb;
            [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)]
            private readonly Type _proxyBaseType;
            private readonly List<FieldBuilder> _fields;
            private readonly List<MethodInfo> _methodInfos;
 
            internal ProxyBuilder(
                ProxyAssembly assembly,
                TypeBuilder tb,
                [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] Type proxyBaseType)
            {
                _assembly = assembly;
                _tb = tb;
                _proxyBaseType = proxyBaseType;
 
                _fields = new List<FieldBuilder>();
                _fields.Add(tb.DefineField("_methodInfos", typeof(MethodInfo[]), FieldAttributes.Private));
 
                _methodInfos = new List<MethodInfo>();
 
                _assembly.EnsureTypeIsVisible(proxyBaseType);
            }
 
            private void Complete()
            {
                Type[] args = new Type[_fields.Count];
                for (int i = 0; i < args.Length; i++)
                {
                    args[i] = _fields[i].FieldType;
                }
 
                ConstructorBuilder cb = _tb.DefineConstructor(MethodAttributes.Public, CallingConventions.HasThis, args);
                ILGenerator il = cb.GetILGenerator();
 
                // chained ctor call
                ConstructorInfo baseCtor = _proxyBaseType.GetConstructor(Type.EmptyTypes)!;
                Debug.Assert(baseCtor != null);
 
                il.Emit(OpCodes.Ldarg_0);
                il.Emit(OpCodes.Call, baseCtor);
 
                // store all the fields
                for (int i = 0; i < args.Length; i++)
                {
                    il.Emit(OpCodes.Ldarg_0);
                    il.Emit(OpCodes.Ldarg, i + 1);
                    il.Emit(OpCodes.Stfld, _fields[i]);
                }
 
                il.Emit(OpCodes.Ret);
            }
 
            internal GeneratedTypeInfo CreateType()
            {
                this.Complete();
                return new GeneratedTypeInfo(_tb.CreateType(), _methodInfos.ToArray());
            }
 
            internal void AddInterfaceImpl([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] Type iface)
            {
                // If necessary, generate an attribute to permit visibility
                // to internal types.
                _assembly.EnsureTypeIsVisible(iface);
 
                _tb.AddInterfaceImplementation(iface);
 
                // AccessorMethods -> Metadata mappings.
                var propertyMap = new Dictionary<MethodInfo, PropertyAccessorInfo>();
                foreach (PropertyInfo pi in iface.GetRuntimeProperties())
                {
                    var ai = new PropertyAccessorInfo(pi.GetMethod, pi.SetMethod);
                    if (pi.GetMethod != null)
                        propertyMap[pi.GetMethod] = ai;
                    if (pi.SetMethod != null)
                        propertyMap[pi.SetMethod] = ai;
                }
 
                var eventMap = new Dictionary<MethodInfo, EventAccessorInfo>();
                foreach (EventInfo ei in iface.GetRuntimeEvents())
                {
                    var ai = new EventAccessorInfo(ei.AddMethod, ei.RemoveMethod, ei.RaiseMethod);
                    if (ei.AddMethod != null)
                        eventMap[ei.AddMethod] = ai;
                    if (ei.RemoveMethod != null)
                        eventMap[ei.RemoveMethod] = ai;
                    if (ei.RaiseMethod != null)
                        eventMap[ei.RaiseMethod] = ai;
                }
 
                foreach (MethodInfo mi in iface.GetRuntimeMethods())
                {
                    // Skip regular/non-virtual instance methods, static methods, and methods that cannot be overridden
                    // ("methods that cannot be overridden" includes default implementation of other interface methods).
                    if (!mi.IsVirtual || mi.IsFinal)
                        continue;
 
                    int methodInfoIndex = _methodInfos.Count;
                    _methodInfos.Add(mi);
                    MethodBuilder mdb = AddMethodImpl(mi, methodInfoIndex);
                    if (propertyMap.TryGetValue(mi, out PropertyAccessorInfo? associatedProperty))
                    {
                        if (mi.Equals(associatedProperty.InterfaceGetMethod))
                            associatedProperty.GetMethodBuilder = mdb;
                        else
                            associatedProperty.SetMethodBuilder = mdb;
                    }
 
                    if (eventMap.TryGetValue(mi, out EventAccessorInfo? associatedEvent))
                    {
                        if (mi.Equals(associatedEvent.InterfaceAddMethod))
                            associatedEvent.AddMethodBuilder = mdb;
                        else if (mi.Equals(associatedEvent.InterfaceRemoveMethod))
                            associatedEvent.RemoveMethodBuilder = mdb;
                        else
                            associatedEvent.RaiseMethodBuilder = mdb;
                    }
                }
 
                foreach (PropertyInfo pi in iface.GetRuntimeProperties())
                {
                    PropertyAccessorInfo ai = propertyMap[pi.GetMethod ?? pi.SetMethod!];
 
                    // If we didn't make an overridden accessor above, this was a static property, non-virtual property,
                    // or a default implementation of a property of a different interface. In any case, we don't need
                    // to redeclare it.
                    if (ai.GetMethodBuilder == null && ai.SetMethodBuilder == null)
                        continue;
 
                    PropertyBuilder pb = _tb.DefineProperty(pi.Name, pi.Attributes, pi.PropertyType, pi.GetIndexParameters().Select(p => p.ParameterType).ToArray());
                    if (ai.GetMethodBuilder != null)
                        pb.SetGetMethod(ai.GetMethodBuilder);
                    if (ai.SetMethodBuilder != null)
                        pb.SetSetMethod(ai.SetMethodBuilder);
                }
 
                foreach (EventInfo ei in iface.GetRuntimeEvents())
                {
                    EventAccessorInfo ai = eventMap[ei.AddMethod ?? ei.RemoveMethod!];
 
                    // If we didn't make an overridden accessor above, this was a static event, non-virtual event,
                    // or a default implementation of an event of a different interface. In any case, we don't
                    // need to redeclare it.
                    if (ai.AddMethodBuilder == null && ai.RemoveMethodBuilder == null && ai.RaiseMethodBuilder == null)
                        continue;
 
                    Debug.Assert(ei.EventHandlerType != null);
                    EventBuilder eb = _tb.DefineEvent(ei.Name, ei.Attributes, ei.EventHandlerType!);
                    if (ai.AddMethodBuilder != null)
                        eb.SetAddOnMethod(ai.AddMethodBuilder);
                    if (ai.RemoveMethodBuilder != null)
                        eb.SetRemoveOnMethod(ai.RemoveMethodBuilder);
                    if (ai.RaiseMethodBuilder != null)
                        eb.SetRaiseMethod(ai.RaiseMethodBuilder);
                }
            }
 
            private MethodBuilder AddMethodImpl(MethodInfo mi, int methodInfoIndex)
            {
                ParameterInfo[] parameters = mi.GetParameters();
                Type[] paramTypes = new Type[parameters.Length];
                Type[][] paramReqMods = new Type[paramTypes.Length][];
 
                for (int i = 0; i < parameters.Length; i++)
                {
                    paramTypes[i] = parameters[i].ParameterType;
                    paramReqMods[i] = parameters[i].GetRequiredCustomModifiers();
                }
 
                MethodAttributes attributes = MethodAttributes.Public;
 
                attributes |= mi.IsStatic ? MethodAttributes.Static : MethodAttributes.Virtual;
 
                MethodBuilder mdb = _tb.DefineMethod(mi.Name, attributes, CallingConventions.Standard,
                    mi.ReturnType, null, null,
                    paramTypes, paramReqMods, null);
 
                if (mi.ContainsGenericParameters)
                {
                    Type[] ts = mi.GetGenericArguments();
                    string[] ss = new string[ts.Length];
                    for (int i = 0; i < ts.Length; i++)
                    {
                        ss[i] = ts[i].Name;
                    }
                    GenericTypeParameterBuilder[] genericParameters = mdb.DefineGenericParameters(ss);
                    for (int i = 0; i < genericParameters.Length; i++)
                    {
                        genericParameters[i].SetGenericParameterAttributes(ts[i].GenericParameterAttributes);
                    }
                }
                ILGenerator il = mdb.GetILGenerator();
 
                if (mi.IsStatic)
                {
                    ConstructorInfo exCtor = typeof(NotSupportedException).GetConstructor([typeof(string)])!;
 
                    il.Emit(OpCodes.Ldstr, SR.DispatchProxy_Method_Invocation_Cannot_Be_Static);
                    il.Emit(OpCodes.Newobj, exCtor);
                    il.Emit(OpCodes.Throw);
 
                    _tb.DefineMethodOverride(mdb, mi);
                    return mdb;
                }
 
                ParametersArray args = new ParametersArray(il, paramTypes);
 
                // object[] args = new object[paramCount];
                il.Emit(OpCodes.Nop);
                GenericArray<object> argsArr = new GenericArray<object>(il, parameters.Length);
 
                for (int i = 0; i < parameters.Length; i++)
                {
                    // args[i] = argi;
                    bool isOutRef = parameters[i].IsOut && parameters[i].ParameterType.IsByRef && !parameters[i].IsIn;
 
                    if (!isOutRef)
                    {
                        argsArr.BeginSet(i);
                        args.Get(i);
                        argsArr.EndSet(parameters[i].ParameterType);
                    }
                }
 
                // MethodInfo methodInfo = _methodInfos[methodInfoIndex];
                LocalBuilder methodInfoLocal = il.DeclareLocal(typeof(MethodInfo));
                il.Emit(OpCodes.Ldarg_0);
                il.Emit(OpCodes.Ldfld, _fields[MethodInfosFieldAndCtorParameterIndex]); // MethodInfo[] _methodInfos
                il.Emit(OpCodes.Ldc_I4, methodInfoIndex);
                il.Emit(OpCodes.Ldelem_Ref);
                il.Emit(OpCodes.Stloc, methodInfoLocal);
 
                if (mi.ContainsGenericParameters)
                {
                    // methodInfo = methodInfo.MakeGenericMethod(mi.GetGenericArguments());
                    il.Emit(OpCodes.Ldloc, methodInfoLocal);
 
                    Type[] genericTypes = mi.GetGenericArguments();
                    GenericArray<Type> typeArr = new GenericArray<Type>(il, genericTypes.Length);
                    for (int i = 0; i < genericTypes.Length; ++i)
                    {
                        typeArr.BeginSet(i);
                        il.Emit(OpCodes.Ldtoken, genericTypes[i]);
                        il.Emit(OpCodes.Call, s_getTypeFromHandleMethod);
                        typeArr.EndSet(typeof(Type));
                    }
                    typeArr.Load();
 
                    il.Emit(OpCodes.Callvirt, s_makeGenericMethodMethod);
                    il.Emit(OpCodes.Stloc, methodInfoLocal);
                }
 
                // object result = this.Invoke(methodInfo, args);
                LocalBuilder? resultLocal = mi.ReturnType != typeof(void) ?
                    il.DeclareLocal(typeof(object)) :
                    null;
                il.Emit(OpCodes.Ldarg_0);
                il.Emit(OpCodes.Ldloc, methodInfoLocal);
                argsArr.Load();
                il.Emit(OpCodes.Callvirt, s_dispatchProxyInvokeMethod);
 
                if (resultLocal != null)
                {
                    il.Emit(OpCodes.Stloc, resultLocal);
                }
                else
                {
                    // drop the result for void methods
                    il.Emit(OpCodes.Pop);
                }
 
                for (int i = 0; i < parameters.Length; i++)
                {
                    if (parameters[i].ParameterType.IsByRef)
                    {
                        args.BeginSet(i);
                        argsArr.Get(i);
                        args.EndSet(i, typeof(object));
                    }
                }
 
                if (resultLocal != null)
                {
                    // return (mi.ReturnType)result;
                    il.Emit(OpCodes.Ldloc, resultLocal);
                    Convert(il, typeof(object), mi.ReturnType, false);
                }
 
                il.Emit(OpCodes.Ret);
 
                _tb.DefineMethodOverride(mdb, mi);
                return mdb;
            }
 
            private static int GetTypeCode(Type type) => (int)Type.GetTypeCode(type);
 
            private static readonly OpCode[] s_convOpCodes = new OpCode[] {
                OpCodes.Nop, //Empty = 0,
                OpCodes.Nop, //Object = 1,
                OpCodes.Nop, //DBNull = 2,
                OpCodes.Conv_I1, //Boolean = 3,
                OpCodes.Conv_I2, //Char = 4,
                OpCodes.Conv_I1, //SByte = 5,
                OpCodes.Conv_U1, //Byte = 6,
                OpCodes.Conv_I2, //Int16 = 7,
                OpCodes.Conv_U2, //UInt16 = 8,
                OpCodes.Conv_I4, //Int32 = 9,
                OpCodes.Conv_U4, //UInt32 = 10,
                OpCodes.Conv_I8, //Int64 = 11,
                OpCodes.Conv_U8, //UInt64 = 12,
                OpCodes.Conv_R4, //Single = 13,
                OpCodes.Conv_R8, //Double = 14,
                OpCodes.Nop, //Decimal = 15,
                OpCodes.Nop, //DateTime = 16,
                OpCodes.Nop, //17
                OpCodes.Nop, //String = 18,
            };
 
            private static readonly OpCode[] s_ldindOpCodes = new OpCode[] {
                OpCodes.Nop, //Empty = 0,
                OpCodes.Nop, //Object = 1,
                OpCodes.Nop, //DBNull = 2,
                OpCodes.Ldind_I1, //Boolean = 3,
                OpCodes.Ldind_I2, //Char = 4,
                OpCodes.Ldind_I1, //SByte = 5,
                OpCodes.Ldind_U1, //Byte = 6,
                OpCodes.Ldind_I2, //Int16 = 7,
                OpCodes.Ldind_U2, //UInt16 = 8,
                OpCodes.Ldind_I4, //Int32 = 9,
                OpCodes.Ldind_U4, //UInt32 = 10,
                OpCodes.Ldind_I8, //Int64 = 11,
                OpCodes.Ldind_I8, //UInt64 = 12,
                OpCodes.Ldind_R4, //Single = 13,
                OpCodes.Ldind_R8, //Double = 14,
                OpCodes.Nop, //Decimal = 15,
                OpCodes.Nop, //DateTime = 16,
                OpCodes.Nop, //17
                OpCodes.Ldind_Ref, //String = 18,
            };
 
            private static readonly OpCode[] s_stindOpCodes = new OpCode[] {
                OpCodes.Nop, //Empty = 0,
                OpCodes.Nop, //Object = 1,
                OpCodes.Nop, //DBNull = 2,
                OpCodes.Stind_I1, //Boolean = 3,
                OpCodes.Stind_I2, //Char = 4,
                OpCodes.Stind_I1, //SByte = 5,
                OpCodes.Stind_I1, //Byte = 6,
                OpCodes.Stind_I2, //Int16 = 7,
                OpCodes.Stind_I2, //UInt16 = 8,
                OpCodes.Stind_I4, //Int32 = 9,
                OpCodes.Stind_I4, //UInt32 = 10,
                OpCodes.Stind_I8, //Int64 = 11,
                OpCodes.Stind_I8, //UInt64 = 12,
                OpCodes.Stind_R4, //Single = 13,
                OpCodes.Stind_R8, //Double = 14,
                OpCodes.Nop, //Decimal = 15,
                OpCodes.Nop, //DateTime = 16,
                OpCodes.Nop, //17
                OpCodes.Stind_Ref, //String = 18,
            };
 
            private static void Convert(ILGenerator il, Type source, Type target, bool isAddress)
            {
                Debug.Assert(!target.IsByRef);
                if (target == source)
                    return;
 
                if (source.IsByRef)
                {
                    Debug.Assert(!isAddress);
                    Type argType = source.GetElementType()!;
                    Ldind(il, argType);
                    Convert(il, argType, target, isAddress);
                    return;
                }
                if (target.IsValueType)
                {
                    if (source.IsValueType)
                    {
                        OpCode opCode = s_convOpCodes[GetTypeCode(target)];
                        Debug.Assert(!opCode.Equals(OpCodes.Nop));
                        il.Emit(opCode);
                    }
                    else
                    {
                        Debug.Assert(source.IsAssignableFrom(target));
                        il.Emit(OpCodes.Unbox, target);
                        if (!isAddress)
                            Ldind(il, target);
                    }
                }
                else if (target.IsAssignableFrom(source))
                {
                    if (source.IsValueType || source.IsGenericParameter)
                    {
                        if (isAddress)
                            Ldind(il, source);
                        il.Emit(OpCodes.Box, source);
                    }
                }
                else
                {
                    Debug.Assert(source.IsAssignableFrom(target) || target.IsInterface || source.IsInterface);
                    if (target.IsGenericParameter)
                    {
                        il.Emit(OpCodes.Unbox_Any, target);
                    }
                    else
                    {
                        il.Emit(OpCodes.Castclass, target);
                    }
                }
            }
 
            private static void Ldind(ILGenerator il, Type type)
            {
                OpCode opCode = s_ldindOpCodes[GetTypeCode(type)];
                if (!opCode.Equals(OpCodes.Nop))
                {
                    il.Emit(opCode);
                }
                else
                {
                    il.Emit(OpCodes.Ldobj, type);
                }
            }
 
            private static void Stind(ILGenerator il, Type type)
            {
                OpCode opCode = s_stindOpCodes[GetTypeCode(type)];
                if (!opCode.Equals(OpCodes.Nop))
                {
                    il.Emit(opCode);
                }
                else
                {
                    il.Emit(OpCodes.Stobj, type);
                }
            }
 
            private sealed class ParametersArray
            {
                private readonly ILGenerator _il;
                private readonly Type[] _paramTypes;
                internal ParametersArray(ILGenerator il, Type[] paramTypes)
                {
                    _il = il;
                    _paramTypes = paramTypes;
                }
 
                internal void Get(int i)
                {
                    _il.Emit(OpCodes.Ldarg, i + 1);
                }
 
                internal void BeginSet(int i)
                {
                    _il.Emit(OpCodes.Ldarg, i + 1);
                }
 
                internal void EndSet(int i, Type stackType)
                {
                    Debug.Assert(_paramTypes[i].IsByRef);
                    Type argType = _paramTypes[i].GetElementType()!;
                    Convert(_il, stackType, argType, false);
                    Stind(_il, argType);
                }
            }
 
            private sealed class GenericArray<T>
            {
                private readonly ILGenerator _il;
                private readonly LocalBuilder _lb;
                internal GenericArray(ILGenerator il, int len)
                {
                    _il = il;
                    _lb = il.DeclareLocal(typeof(T[]));
 
                    il.Emit(OpCodes.Ldc_I4, len);
                    il.Emit(OpCodes.Newarr, typeof(T));
                    il.Emit(OpCodes.Stloc, _lb);
                }
 
                internal void Load()
                {
                    _il.Emit(OpCodes.Ldloc, _lb);
                }
 
                internal void Get(int i)
                {
                    _il.Emit(OpCodes.Ldloc, _lb);
                    _il.Emit(OpCodes.Ldc_I4, i);
                    _il.Emit(OpCodes.Ldelem_Ref);
                }
 
                internal void BeginSet(int i)
                {
                    _il.Emit(OpCodes.Ldloc, _lb);
                    _il.Emit(OpCodes.Ldc_I4, i);
                }
 
                internal void EndSet(Type stackType)
                {
                    Convert(_il, stackType, typeof(T), false);
                    _il.Emit(OpCodes.Stelem_Ref);
                }
            }
 
            private sealed class PropertyAccessorInfo
            {
                public MethodInfo? InterfaceGetMethod { get; }
                public MethodInfo? InterfaceSetMethod { get; }
                public MethodBuilder? GetMethodBuilder { get; set; }
                public MethodBuilder? SetMethodBuilder { get; set; }
 
                public PropertyAccessorInfo(MethodInfo? interfaceGetMethod, MethodInfo? interfaceSetMethod)
                {
                    InterfaceGetMethod = interfaceGetMethod;
                    InterfaceSetMethod = interfaceSetMethod;
                }
            }
 
            private sealed class EventAccessorInfo
            {
                public MethodInfo? InterfaceAddMethod { get; }
                public MethodInfo? InterfaceRemoveMethod { get; }
                public MethodInfo? InterfaceRaiseMethod { get; }
                public MethodBuilder? AddMethodBuilder { get; set; }
                public MethodBuilder? RemoveMethodBuilder { get; set; }
                public MethodBuilder? RaiseMethodBuilder { get; set; }
 
                public EventAccessorInfo(MethodInfo? interfaceAddMethod, MethodInfo? interfaceRemoveMethod, MethodInfo? interfaceRaiseMethod)
                {
                    InterfaceAddMethod = interfaceAddMethod;
                    InterfaceRemoveMethod = interfaceRemoveMethod;
                    InterfaceRaiseMethod = interfaceRaiseMethod;
                }
            }
        }
    }
}