当通过 TypeBuilder.CreateType 实现具有带有 'in' 参数的方法的接口时,抛出 TypeLoadException

Cod*_*ion 8 c# reflection.emit pass-by-reference typebuilder

使用TypeBuilder,我正在构建一个实现包含方法的接口的类。使用 实现该方法后ILGenerator,然后我调用TypeBuilder.CreateType(),在正常情况下一切顺利。但是,如果该方法包含带有in修饰符的任何参数(也称为值类型的只读引用)TypeBuilder.CreateType() ,则会抛出TypeLoadException("Method 'SomeMethod' ... does not have an implementation.")

与通常情况下TypeLoadException所实现的方法与接口中声明的方法不存在相同的签名不同,只有当方法包含in参数(即使签名相同)时才会出现此问题。当我删除或更改in修饰符为refor时outTypeBuilder.CreateType()成功地将生成的方法识别为接口中声明的方法的实现,并且类型正常构建。

这是一个完全可编译的示例:

using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Reflection;
using System.Reflection.Emit;
using System.Threading;
 
namespace EmitMethodWithInParamTest
{
    public struct StructParam
    {
        public String Data;
    }
 
    public interface ISomeInterface
    {
        Int32 SomeMethod(in StructParam param);
    }
 
    static class EmitExtension
    {
        public static void ReplicateCustomAttributes(this ParameterBuilder paramBuilder, ParameterInfo paramInfo)
        {
            foreach (var attrData in paramInfo.GetCustomAttributesData())
            {
                var ctorArgs = attrData.ConstructorArguments.Select(arg => arg.Value).ToArray();
 
                // Handling variable arguments
                var ctorParamInfos = attrData.Constructor.GetParameters();
                if (ctorParamInfos.Length > 0 &&
                    ctorParamInfos.Last().IsDefined(typeof(ParamArrayAttribute)) &&
                    ctorArgs.Last() is IReadOnlyCollection<CustomAttributeTypedArgument> variableArgs)
                {
                    ctorArgs[ctorArgs.Length - 1] = variableArgs.Select(arg => arg.Value).ToArray();
                }
 
                var namedPropArgs = attrData.NamedArguments.Where(arg => !arg.IsField);
                var namedPropInfos = namedPropArgs.Select(arg => (PropertyInfo)arg.MemberInfo).ToArray();
                var namedPropValues = namedPropArgs.Select(arg => arg.TypedValue.Value).ToArray();
 
                var namedFieldArgs = attrData.NamedArguments.Where(arg => arg.IsField);
                var namedFieldInfos = namedFieldArgs.Select(arg => (FieldInfo)arg.MemberInfo).ToArray();
                var namedFieldValues = namedFieldArgs.Select(arg => arg.TypedValue.Value).ToArray();
 
                var attrBuilder = new CustomAttributeBuilder(attrData.Constructor,
                    ctorArgs, namedPropInfos, namedPropValues, namedFieldInfos, namedFieldValues);
                paramBuilder.SetCustomAttribute(attrBuilder);
            }
        }
    }
 
    class Program
    {
        static Program()
        {
            Thread.CurrentThread.CurrentUICulture = CultureInfo.GetCultureInfo("en-us");
        }
 
        static void Main(String[] args)
        {
            var assemblyBuilder = AssemblyBuilder.DefineDynamicAssembly(new AssemblyName("DynamicAssembly"), AssemblyBuilderAccess.Run);
            var moduleBuilder = assemblyBuilder.DefineDynamicModule("MainModule");
            var typeBuilder = moduleBuilder.DefineType("SomeClass",
                TypeAttributes.Public | TypeAttributes.Class | TypeAttributes.AutoClass | TypeAttributes.AnsiClass | TypeAttributes.BeforeFieldInit | TypeAttributes.AutoLayout,
                null /*base class*/,
                new[] { typeof(ISomeInterface) });
 
            var methodInfoToImpl = typeof(ISomeInterface).GetMethod(nameof(ISomeInterface.SomeMethod));
            var paramInfos = methodInfoToImpl.GetParameters();
 
            var methodBuilder = typeBuilder.DefineMethod(methodInfoToImpl.Name,
                MethodAttributes.Public | MethodAttributes.Virtual | MethodAttributes.Final,
                CallingConventions.HasThis,
                methodInfoToImpl.ReturnType,
                paramInfos.Select(pi => pi.ParameterType).ToArray());
 
            foreach (var paramInfo in paramInfos)
            {
                // paramInfo.Position is zero-based but DefineParameter requires 1-based index.
                var paramBuilder = methodBuilder.DefineParameter(paramInfo.Position + 1, paramInfo.Attributes, paramInfo.Name);
                if (paramInfo.Attributes.HasFlag(ParameterAttributes.HasDefault))
                {
                    paramBuilder.SetConstant(paramInfo.DefaultValue);
                }
                paramBuilder.ReplicateCustomAttributes(paramInfo);
            }
 
            // Dummy implementation for example. Always throws NotImplementedException.
            var ilGen = methodBuilder.GetILGenerator();
            ilGen.Emit(OpCodes.Newobj, typeof(NotImplementedException).GetConstructor(Type.EmptyTypes));
            ilGen.Emit(OpCodes.Throw);
 
            var builtType = typeBuilder.CreateType();               // <- TypeLoadException("Method 'SomeMethod' in type 'SomeClass' from assembly 'DynamicAssembly, ...' does not have an implementation.") is thrown.
            var generatedObj = (ISomeInterface)Activator.CreateInstance(builtType);
 
            var someParam = new StructParam() { Data = "SomeData" };
            var result = generatedObj.SomeMethod(in someParam);     // <- NotImplementedException expected by dummy implementation if executed.
 
            Console.WriteLine($"Result: {result}");
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

此代码也上传到Pastebin

在深入研究这个问题时,我发现该in参数有两个自定义属性,InteropServices.InAttributeCompilerServices.IsReadOnlyAttribute。但是,当我生成一个方法而不实现该接口时(这通常会成功,因为不需要签名匹配)in生成的方法的参数只有一个自定义属性,InAttribute. 因此,我从接口复制了参数的所有自定义属性,但仍然引发 TypeLoadException。

我已经在.NET Framework 4.6.1.NET Core 2.2上对此进行了测试C# 7.2 and 7.3。所有环境都给了我同样的例外。我在 Windows 上使用 Visual Studio 2017。

有什么我错过的事情或者有什么解决方法吗?

感谢您提前提供的任何帮助。

Cod*_*ion 5

写完上面的问题后,我花了几天时间研究了IL中示例代码的构建二进制文件和CoreCLR的源代码,现在我找到了问题和解决方案。

简而言之,返回类型和每个参数类型的必需和可选自定义修饰符像每种类型一样采用方法签名的一部分,并且必须手动复制。我认为这将通过传递ParameterAttributes.InMethodBuilder.DefineParameter复制自定义属性来完成InAttribute,但这是错误的。

并且,在inrefout修饰符中,仅in向指定参数发出所需的自定义修饰符。相反,refout仅用其类型本身来表示。in这就是为什么没有按预期工作的原因。

要复制自定义修饰符,请调用TypeBuilder.DefineMethod需要修改的方法,如下所示:

var methodBuilder = typeBuilder.DefineMethod(methodInfoToImpl.Name,
    MethodAttributes.Public | MethodAttributes.Virtual | MethodAttributes.Final,
    CallingConventions.HasThis,
    methodInfoToImpl.ReturnType,
    methodInfoToImpl.ReturnParameter.GetRequiredCustomModifiers(),      // *
    methodInfoToImpl.ReturnParameter.GetOptionalCustomModifiers(),      // *
    paramInfos.Select(pi => pi.ParameterType).ToArray(),
    paramInfos.Select(pi => pi.GetRequiredCustomModifiers()).ToArray(), // *
    paramInfos.Select(pi => pi.GetOptionalCustomModifiers()).ToArray()  // *
    );
Run Code Online (Sandbox Code Playgroud)

新添加了标记行// *以复制返回/参数类型的自定义修饰符。

或者,我们可以通过在没有任何类型和自定义修饰符参数的情况MethodBuilder.SetSignature下调用后调用方法来完成此操作。DefineMethod如果我们决定单独调用SetSignature,我们需要在属性的任何DefineParameterSetCustomAttributeEquals(Object)SetImplementationFlagsgetterSignature以及许多其他调用MethodBuilder.GetMethodSignature()缓存表示方法签名的字节的内部方法的方法之前调用它。