Reflection.Emit 实现接口的泛型方法约束
Reflection.Emit implementing generic method constraints of interface
我正在尝试使用 System.Reflection.Emit
动态生成接口的实现。为了能够生成泛型方法的实现,我必须正确地将接口方法的所有泛型参数约束应用于生成的 class 实现它的方法,但我无法弄清楚我在做错什么 base class 约束条件。
尝试构建类型时,我收到以下错误(已翻译):
System.TypeLoadException: 'The method "Error" in type "TestImplementation" of Assembly "TestAsm, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null" tried to implicitly implement an interface method with weaker constraints for a type parameter.'
这是一个简单的示例界面:
public interface ITest
{
//Base type constraint seems to cause issues, also reproduces with other classes
void Error<T>() where T : Encoding;
// these all work as expected when code is generated for them
//Task<T> A<T>(T input) where T : struct;
//Task<T> B<T>(T input) where T : class, new();
//Task<T> C<T>(T input) where T : IComparable<string>, IComparable, ICloneable;
}
这是类型生成器:
internal class Program
{
private static Type Build()
{
// quite a lot of boilerplate is necessary for this, sorry!
var asm = AssemblyBuilder.DefineDynamicAssembly(new AssemblyName("TestAsm"), AssemblyBuilderAccess.Run);
var module = asm.DefineDynamicModule(asm.GetName().Name);
var type = module.DefineType(
"TestImplementation",
TypeAttributes.Class | TypeAttributes.Public | TypeAttributes.AutoLayout | TypeAttributes.AnsiClass | TypeAttributes.BeforeFieldInit,
typeof(object),
new[] { typeof(ITest) }
);
var method = typeof(ITest).GetMethod("Error");
var m = type.DefineMethod(
method.Name,
MethodAttributes.Public | MethodAttributes.Final | MethodAttributes.HideBySig | MethodAttributes.Virtual | MethodAttributes.NewSlot,
CallingConventions.Standard | CallingConventions.HasThis,
typeof(void),
new Type[0]
);
//this is where the constraints are applied, I assume something is missing here
var constraint = method.GetGenericArguments()[0];
var constraintBuilder = m.DefineGenericParameters("T")[0];
constraintBuilder.SetBaseTypeConstraint(constraint.BaseType);
constraintBuilder.SetInterfaceConstraints(constraint.GetInterfaces());
constraintBuilder.SetGenericParameterAttributes(constraint.GenericParameterAttributes);
foreach (var attribute in BuildCustomAttributes(constraint.GetCustomAttributesData()))
{
constraintBuilder.SetCustomAttribute(attribute);
}
// dummy method body
var il = m.GetILGenerator();
il.EmitWriteLine("Sucess!");
il.Emit(OpCodes.Ret);
//fails right here \/
return type.CreateType();
}
// I don't think attributes are actually necessary, but just in case..
private static IEnumerable<CustomAttributeBuilder> BuildCustomAttributes(IEnumerable<CustomAttributeData> customAttributes)
{
return customAttributes.Select(attribute =>
{
var attributeArgs = attribute.ConstructorArguments.Select(a => a.Value).ToArray();
var namedPropertyInfos = attribute.NamedArguments.Select(a => a.MemberInfo).OfType<PropertyInfo>().ToArray();
var namedPropertyValues = attribute.NamedArguments.Where(a => a.MemberInfo is PropertyInfo).Select(a => a.TypedValue.Value).ToArray();
var namedFieldInfos = attribute.NamedArguments.Select(a => a.MemberInfo).OfType<FieldInfo>().ToArray();
var namedFieldValues = attribute.NamedArguments.Where(a => a.MemberInfo is FieldInfo).Select(a => a.TypedValue.Value).ToArray();
return new CustomAttributeBuilder(attribute.Constructor, attributeArgs, namedPropertyInfos, namedPropertyValues, namedFieldInfos, namedFieldValues);
});
}
private static void Main(string[] args)
{
var t = Build();
var instance = (ITest)Activator.CreateInstance(t);
instance.Error<List<object>>();
}
}
我尝试过的:
- 添加了我之前认为不必要的 CustomAttributes 生成
- 阅读有关发出泛型类型和方法的 MSDN 文章,但这并没有帮助我弄清楚为什么约束不等效
- 尝试了其他可能的约束,只有基础 class 约束似乎失败了
- 使用
constraint
而不是 constraint.BaseType
用于 SetBaseTypeConstraint
- 使用
TypeBuilder.CreateMethodOverride
使用显式实现,这只是将错误消息从 'implicit' 更改为 'explicit'
- 将示例项目创建为 .net 框架应用程序而不是 .net 标准 class 库
我的约束生成中是否缺少某些内容?我希望调用 constraintBuilder.SetBaseTypeConstraint(constraint.BaseType);
来设置基本 classes.
的约束
设置任何接口约束要么覆盖 BaseType 约束,要么导致误导性错误消息。这解决了问题:
if(constraint.BaseType != null)
{
constraintBuilder.SetBaseTypeConstraint(constraint.BaseType);
}
else
{
constraintBuilder.SetInterfaceConstraints(constraint.GetInterfaces());
}
这看起来确实违反直觉,因为我现在应用 FEWER 约束来修复一个错误,说我应用的约束太少。这也适用于像
这样的声明
void Test<T>() where T: Example, IComparable<Example>`
尽管我认为不会,因为如果存在基础 class 约束,我将不再应用接口约束。这看起来很奇怪,所以我决定进一步调查并得出结论,GetInterfaces()
方法返回正确的接口是巧合或实现细节。该文档没有提及这一点,而是建议使用 GetGenericParameterConstraints
方法。
这是我最终实现的解决方案:
constraintBuilder.SetBaseTypeConstraint(constraint.BaseType);
constraintBuilder.SetInterfaceConstraints(constraint.GetInterfaces());
被替换为
var interfaceList = new List<Type>();
foreach (var restriction in constraint.GetGenericParameterConstraints())
{
if (restriction.IsClass)
{
constraintBuilder.SetBaseTypeConstraint(restriction);
}
else
{
interfaceList.Add(restriction);
}
}
if (interfaceList.Count > 0)
{
constraintBuilder.SetInterfaceConstraints(interfaceList.ToArray());
}
我正在尝试使用 System.Reflection.Emit
动态生成接口的实现。为了能够生成泛型方法的实现,我必须正确地将接口方法的所有泛型参数约束应用于生成的 class 实现它的方法,但我无法弄清楚我在做错什么 base class 约束条件。
尝试构建类型时,我收到以下错误(已翻译):
System.TypeLoadException: 'The method "Error" in type "TestImplementation" of Assembly "TestAsm, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null" tried to implicitly implement an interface method with weaker constraints for a type parameter.'
这是一个简单的示例界面:
public interface ITest
{
//Base type constraint seems to cause issues, also reproduces with other classes
void Error<T>() where T : Encoding;
// these all work as expected when code is generated for them
//Task<T> A<T>(T input) where T : struct;
//Task<T> B<T>(T input) where T : class, new();
//Task<T> C<T>(T input) where T : IComparable<string>, IComparable, ICloneable;
}
这是类型生成器:
internal class Program
{
private static Type Build()
{
// quite a lot of boilerplate is necessary for this, sorry!
var asm = AssemblyBuilder.DefineDynamicAssembly(new AssemblyName("TestAsm"), AssemblyBuilderAccess.Run);
var module = asm.DefineDynamicModule(asm.GetName().Name);
var type = module.DefineType(
"TestImplementation",
TypeAttributes.Class | TypeAttributes.Public | TypeAttributes.AutoLayout | TypeAttributes.AnsiClass | TypeAttributes.BeforeFieldInit,
typeof(object),
new[] { typeof(ITest) }
);
var method = typeof(ITest).GetMethod("Error");
var m = type.DefineMethod(
method.Name,
MethodAttributes.Public | MethodAttributes.Final | MethodAttributes.HideBySig | MethodAttributes.Virtual | MethodAttributes.NewSlot,
CallingConventions.Standard | CallingConventions.HasThis,
typeof(void),
new Type[0]
);
//this is where the constraints are applied, I assume something is missing here
var constraint = method.GetGenericArguments()[0];
var constraintBuilder = m.DefineGenericParameters("T")[0];
constraintBuilder.SetBaseTypeConstraint(constraint.BaseType);
constraintBuilder.SetInterfaceConstraints(constraint.GetInterfaces());
constraintBuilder.SetGenericParameterAttributes(constraint.GenericParameterAttributes);
foreach (var attribute in BuildCustomAttributes(constraint.GetCustomAttributesData()))
{
constraintBuilder.SetCustomAttribute(attribute);
}
// dummy method body
var il = m.GetILGenerator();
il.EmitWriteLine("Sucess!");
il.Emit(OpCodes.Ret);
//fails right here \/
return type.CreateType();
}
// I don't think attributes are actually necessary, but just in case..
private static IEnumerable<CustomAttributeBuilder> BuildCustomAttributes(IEnumerable<CustomAttributeData> customAttributes)
{
return customAttributes.Select(attribute =>
{
var attributeArgs = attribute.ConstructorArguments.Select(a => a.Value).ToArray();
var namedPropertyInfos = attribute.NamedArguments.Select(a => a.MemberInfo).OfType<PropertyInfo>().ToArray();
var namedPropertyValues = attribute.NamedArguments.Where(a => a.MemberInfo is PropertyInfo).Select(a => a.TypedValue.Value).ToArray();
var namedFieldInfos = attribute.NamedArguments.Select(a => a.MemberInfo).OfType<FieldInfo>().ToArray();
var namedFieldValues = attribute.NamedArguments.Where(a => a.MemberInfo is FieldInfo).Select(a => a.TypedValue.Value).ToArray();
return new CustomAttributeBuilder(attribute.Constructor, attributeArgs, namedPropertyInfos, namedPropertyValues, namedFieldInfos, namedFieldValues);
});
}
private static void Main(string[] args)
{
var t = Build();
var instance = (ITest)Activator.CreateInstance(t);
instance.Error<List<object>>();
}
}
我尝试过的:
- 添加了我之前认为不必要的 CustomAttributes 生成
- 阅读有关发出泛型类型和方法的 MSDN 文章,但这并没有帮助我弄清楚为什么约束不等效
- 尝试了其他可能的约束,只有基础 class 约束似乎失败了
- 使用
constraint
而不是constraint.BaseType
用于SetBaseTypeConstraint
- 使用
TypeBuilder.CreateMethodOverride
使用显式实现,这只是将错误消息从 'implicit' 更改为 'explicit' - 将示例项目创建为 .net 框架应用程序而不是 .net 标准 class 库
我的约束生成中是否缺少某些内容?我希望调用 constraintBuilder.SetBaseTypeConstraint(constraint.BaseType);
来设置基本 classes.
设置任何接口约束要么覆盖 BaseType 约束,要么导致误导性错误消息。这解决了问题:
if(constraint.BaseType != null)
{
constraintBuilder.SetBaseTypeConstraint(constraint.BaseType);
}
else
{
constraintBuilder.SetInterfaceConstraints(constraint.GetInterfaces());
}
这看起来确实违反直觉,因为我现在应用 FEWER 约束来修复一个错误,说我应用的约束太少。这也适用于像
这样的声明void Test<T>() where T: Example, IComparable<Example>`
尽管我认为不会,因为如果存在基础 class 约束,我将不再应用接口约束。这看起来很奇怪,所以我决定进一步调查并得出结论,GetInterfaces()
方法返回正确的接口是巧合或实现细节。该文档没有提及这一点,而是建议使用 GetGenericParameterConstraints
方法。
这是我最终实现的解决方案:
constraintBuilder.SetBaseTypeConstraint(constraint.BaseType);
constraintBuilder.SetInterfaceConstraints(constraint.GetInterfaces());
被替换为
var interfaceList = new List<Type>();
foreach (var restriction in constraint.GetGenericParameterConstraints())
{
if (restriction.IsClass)
{
constraintBuilder.SetBaseTypeConstraint(restriction);
}
else
{
interfaceList.Add(restriction);
}
}
if (interfaceList.Count > 0)
{
constraintBuilder.SetInterfaceConstraints(interfaceList.ToArray());
}