gpt4 book ai didi

c# - 当通过 TypeBuilder.CreateType 实现具有参数为 'in' 的方法的接口(interface)时,抛出 TypeLoadException

转载 作者:太空宇宙 更新时间:2023-11-03 12:02:15 24 4
gpt4 key购买 nike

在开始之前,这是我关于 SO 的第一个问题。因此,可能存在错误或缺少有关该问题的信息。如果我需要更正某些内容,请告诉我。谢谢。


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

TypeLoadException 的常见情况不同,它实现了与接口(interface)中声明的方法具有相同签名的方法不存在,只有当方法包含 时才会引发此问题在 parameter(s) 中甚至签名都是相同的。当我删除 in 修饰符或将其更改为 refout 时,TypeBuilder.CreateType() 成功识别生成的方法作为接口(interface)中声明的方法的实现,类型正常构建。

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

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}");
}
}
}

此代码也上传到Pastebin .

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

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

有什么我遗漏的或有任何解决方法吗?

提前感谢您的帮助。

最佳答案

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

简而言之,返回类型和每个参数类型的必需和可选自定义修饰符像每个类型一样占用方法签名的一部分,并且必须手动复制。我认为这将通过将 ParameterAttributes.In 传递给 MethodBuilder.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() // *
);

新添加带有//*的标记行,用于复制返回/参数类型的自定义修饰符。

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

感谢您阅读并给我建议。 :)

关于c# - 当通过 TypeBuilder.CreateType 实现具有参数为 'in' 的方法的接口(interface)时,抛出 TypeLoadException,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/56564992/

24 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com