生成正确的 IL 以在泛型 class 上使用带有新 "in" 修饰符的参数调用虚方法
Generate correct IL to call a virtual method with a parameter with the new "in" modifier on a generic class
我正在围绕 .NET Core 2.1 中的新 System.IO.Pipelines
包编写 serialization/deserialization framework。我 运行 在生成 IL 来调用带有参数的虚拟方法时遇到了一个问题,该参数在泛型 class 上带有新的 "in" 修饰符。这基本上是我要调用的方法签名:
public virtual T DoSomething(in ReadOnlySpan<byte> memory, T o);
如果我去掉 virtual 修饰符,我的代码 运行 没问题。添加 virtual 修饰符后,在尝试调用生成的代码时会出现 MethodNotFound
异常。我还注意到,如果我不在方法参数的任何地方使用 in
修饰符,它仍然可以正常工作。如果我从 class 中删除通用参数(并保留 in
参数),则该调用也适用于 virtual 修饰符。 只有在使用 in
修饰符并且似乎使用泛型类型时才会崩溃。
我已将我的代码缩减为一个最小的示例,您可以在下面看到(对于代码转储,我深表歉意,代码中有很多我认为与整个问题相关的内容)。
using System;
using System.Collections.Generic;
using System.Reflection;
using System.Reflection.Emit;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Text;
namespace MessageStream.Bug
{
public class BugReproduction
{
public static void Main(string[] args)
{
var test = new TestClass<int>();
var span = new ReadOnlySpan<byte>(new byte[] { 1 });
test.OuterDoSomething(span, 10);
}
}
public class TestClass<T> where T : new()
{
private ITestInterface<T> testInterfaceImpl;
public TestClass()
{
Initialize();
}
public T OuterDoSomething(in ReadOnlySpan<byte> memory, T o)
{
return testInterfaceImpl.DoSomething(in memory, o);
}
// Generates a class that implements the ITestInterface<T>.DoSomething
// The generated class basically just calls testClass.DoSomething(in memory, o);
private void Initialize()
{
Type concreteType = GetType();
Type interfaceType = typeof(ITestInterface<T>);
var methodToOverride = interfaceType.GetMethod(nameof(ITestInterface<T>.DoSomething));
string overrideMethodName = string.Format("{0}.{1}", interfaceType.FullName, methodToOverride.Name);
var typeBuilder = CreateTypeBuilderForDeserializer(GetType().Name);
var thisField = typeBuilder.DefineField("testClass", concreteType, FieldAttributes.Private);
var constructor = typeBuilder.DefineConstructor(
MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.RTSpecialName, CallingConventions.HasThis, new[] { concreteType });
var constructorIlGenerator = constructor.GetILGenerator();
constructorIlGenerator.Emit(OpCodes.Ldarg_0);
constructorIlGenerator.Emit(OpCodes.Ldarg_1);
constructorIlGenerator.Emit(OpCodes.Stfld, thisField);
constructorIlGenerator.Emit(OpCodes.Ret);
var doSomethingMethodBuilder = typeBuilder.DefineMethod(
overrideMethodName,
MethodAttributes.Public | MethodAttributes.HideBySig |
MethodAttributes.Virtual | MethodAttributes.Final,
CallingConventions.HasThis,
typeof(T),
new Type[0],
new Type[0],
new[]
{
typeof(ReadOnlySpan<byte>).MakeByRefType(),
typeof(T)
},
new[]
{
new [] { typeof(InAttribute) },
new Type[0]
},
new[]
{
new Type[0],
new Type[0]
});
doSomethingMethodBuilder.DefineParameter(1, ParameterAttributes.In, "memory")
// I pulled this from a decompiled assembly. You will get a signature doesnt match exception if you don't include it.
.SetCustomAttribute(typeof(IsReadOnlyAttribute).GetConstructors()[0], new byte[] { 01, 00, 00, 00 });
doSomethingMethodBuilder.DefineParameter(2, ParameterAttributes.None, "o");
// Build method body
var methodIlGenerator = doSomethingMethodBuilder.GetILGenerator();
// Emit the call to the "DoSomething" method.
// This fails if the virtual keyword is used on the method.
methodIlGenerator.Emit(OpCodes.Ldarg_0);
methodIlGenerator.Emit(OpCodes.Ldfld, thisField);
methodIlGenerator.Emit(OpCodes.Ldarg_1);
methodIlGenerator.Emit(OpCodes.Ldarg_2);
methodIlGenerator.Emit(OpCodes.Callvirt, concreteType.GetMethod("DoSomething"));
methodIlGenerator.Emit(OpCodes.Ret);
// Point the interfaces method to the overidden one.
typeBuilder.DefineMethodOverride(doSomethingMethodBuilder, methodToOverride);
// Create type and create an instance
Type objectType = typeBuilder.CreateType();
testInterfaceImpl = (ITestInterface<T>)Activator.CreateInstance(objectType, this);
}
/// <summary>
/// This will throw a MethodNotFound exception. If you remove virtual it will work though.
/// </summary>
public virtual T DoSomething(in ReadOnlySpan<byte> memory, T o)
{
Console.WriteLine(memory[0]);
Console.WriteLine(o);
return new T();
}
private static TypeBuilder CreateTypeBuilderForDeserializer(string name)
{
var typeSignature = $"{name}{Guid.NewGuid().ToString().Replace("-", "")}";
var an = new AssemblyName(typeSignature);
AssemblyBuilder assemblyBuilder = AssemblyBuilder.DefineDynamicAssembly(an, AssemblyBuilderAccess.Run);
ModuleBuilder moduleBuilder = assemblyBuilder.DefineDynamicModule($"{name}{Guid.NewGuid().ToString()}Module");
TypeBuilder tb = moduleBuilder.DefineType(typeSignature,
TypeAttributes.Public |
TypeAttributes.Class |
TypeAttributes.AutoClass |
TypeAttributes.AnsiClass |
TypeAttributes.BeforeFieldInit |
TypeAttributes.AutoLayout,
null,
new[] { typeof(ITestInterface<T>) });
return tb;
}
}
public interface ITestInterface<T>
{
T DoSomething(in ReadOnlySpan<byte> memory, T o);
}
}
有什么想法吗?几个星期以来,我一直在用头撞墙试图解决这个问题。您可以在 my repository. Check the benchmark project 中找到实际的真实世界代码,以了解其使用情况 on/how。
这是 CoreCLR 中的一个已知错误:
https://github.com/dotnet/corefx/issues/29254
A PR addressing the issue has already been submitted and merged but unfortunately the fix hasn't been released yet. It can be expected 在 .NET Core 2.2.0 中。
在那之前你对此无能为力,正如 this discussion 末尾所说的那样。
我正在围绕 .NET Core 2.1 中的新 System.IO.Pipelines
包编写 serialization/deserialization framework。我 运行 在生成 IL 来调用带有参数的虚拟方法时遇到了一个问题,该参数在泛型 class 上带有新的 "in" 修饰符。这基本上是我要调用的方法签名:
public virtual T DoSomething(in ReadOnlySpan<byte> memory, T o);
如果我去掉 virtual 修饰符,我的代码 运行 没问题。添加 virtual 修饰符后,在尝试调用生成的代码时会出现 MethodNotFound
异常。我还注意到,如果我不在方法参数的任何地方使用 in
修饰符,它仍然可以正常工作。如果我从 class 中删除通用参数(并保留 in
参数),则该调用也适用于 virtual 修饰符。 只有在使用 in
修饰符并且似乎使用泛型类型时才会崩溃。
我已将我的代码缩减为一个最小的示例,您可以在下面看到(对于代码转储,我深表歉意,代码中有很多我认为与整个问题相关的内容)。
using System;
using System.Collections.Generic;
using System.Reflection;
using System.Reflection.Emit;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Text;
namespace MessageStream.Bug
{
public class BugReproduction
{
public static void Main(string[] args)
{
var test = new TestClass<int>();
var span = new ReadOnlySpan<byte>(new byte[] { 1 });
test.OuterDoSomething(span, 10);
}
}
public class TestClass<T> where T : new()
{
private ITestInterface<T> testInterfaceImpl;
public TestClass()
{
Initialize();
}
public T OuterDoSomething(in ReadOnlySpan<byte> memory, T o)
{
return testInterfaceImpl.DoSomething(in memory, o);
}
// Generates a class that implements the ITestInterface<T>.DoSomething
// The generated class basically just calls testClass.DoSomething(in memory, o);
private void Initialize()
{
Type concreteType = GetType();
Type interfaceType = typeof(ITestInterface<T>);
var methodToOverride = interfaceType.GetMethod(nameof(ITestInterface<T>.DoSomething));
string overrideMethodName = string.Format("{0}.{1}", interfaceType.FullName, methodToOverride.Name);
var typeBuilder = CreateTypeBuilderForDeserializer(GetType().Name);
var thisField = typeBuilder.DefineField("testClass", concreteType, FieldAttributes.Private);
var constructor = typeBuilder.DefineConstructor(
MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.RTSpecialName, CallingConventions.HasThis, new[] { concreteType });
var constructorIlGenerator = constructor.GetILGenerator();
constructorIlGenerator.Emit(OpCodes.Ldarg_0);
constructorIlGenerator.Emit(OpCodes.Ldarg_1);
constructorIlGenerator.Emit(OpCodes.Stfld, thisField);
constructorIlGenerator.Emit(OpCodes.Ret);
var doSomethingMethodBuilder = typeBuilder.DefineMethod(
overrideMethodName,
MethodAttributes.Public | MethodAttributes.HideBySig |
MethodAttributes.Virtual | MethodAttributes.Final,
CallingConventions.HasThis,
typeof(T),
new Type[0],
new Type[0],
new[]
{
typeof(ReadOnlySpan<byte>).MakeByRefType(),
typeof(T)
},
new[]
{
new [] { typeof(InAttribute) },
new Type[0]
},
new[]
{
new Type[0],
new Type[0]
});
doSomethingMethodBuilder.DefineParameter(1, ParameterAttributes.In, "memory")
// I pulled this from a decompiled assembly. You will get a signature doesnt match exception if you don't include it.
.SetCustomAttribute(typeof(IsReadOnlyAttribute).GetConstructors()[0], new byte[] { 01, 00, 00, 00 });
doSomethingMethodBuilder.DefineParameter(2, ParameterAttributes.None, "o");
// Build method body
var methodIlGenerator = doSomethingMethodBuilder.GetILGenerator();
// Emit the call to the "DoSomething" method.
// This fails if the virtual keyword is used on the method.
methodIlGenerator.Emit(OpCodes.Ldarg_0);
methodIlGenerator.Emit(OpCodes.Ldfld, thisField);
methodIlGenerator.Emit(OpCodes.Ldarg_1);
methodIlGenerator.Emit(OpCodes.Ldarg_2);
methodIlGenerator.Emit(OpCodes.Callvirt, concreteType.GetMethod("DoSomething"));
methodIlGenerator.Emit(OpCodes.Ret);
// Point the interfaces method to the overidden one.
typeBuilder.DefineMethodOverride(doSomethingMethodBuilder, methodToOverride);
// Create type and create an instance
Type objectType = typeBuilder.CreateType();
testInterfaceImpl = (ITestInterface<T>)Activator.CreateInstance(objectType, this);
}
/// <summary>
/// This will throw a MethodNotFound exception. If you remove virtual it will work though.
/// </summary>
public virtual T DoSomething(in ReadOnlySpan<byte> memory, T o)
{
Console.WriteLine(memory[0]);
Console.WriteLine(o);
return new T();
}
private static TypeBuilder CreateTypeBuilderForDeserializer(string name)
{
var typeSignature = $"{name}{Guid.NewGuid().ToString().Replace("-", "")}";
var an = new AssemblyName(typeSignature);
AssemblyBuilder assemblyBuilder = AssemblyBuilder.DefineDynamicAssembly(an, AssemblyBuilderAccess.Run);
ModuleBuilder moduleBuilder = assemblyBuilder.DefineDynamicModule($"{name}{Guid.NewGuid().ToString()}Module");
TypeBuilder tb = moduleBuilder.DefineType(typeSignature,
TypeAttributes.Public |
TypeAttributes.Class |
TypeAttributes.AutoClass |
TypeAttributes.AnsiClass |
TypeAttributes.BeforeFieldInit |
TypeAttributes.AutoLayout,
null,
new[] { typeof(ITestInterface<T>) });
return tb;
}
}
public interface ITestInterface<T>
{
T DoSomething(in ReadOnlySpan<byte> memory, T o);
}
}
有什么想法吗?几个星期以来,我一直在用头撞墙试图解决这个问题。您可以在 my repository. Check the benchmark project 中找到实际的真实世界代码,以了解其使用情况 on/how。
这是 CoreCLR 中的一个已知错误: https://github.com/dotnet/corefx/issues/29254
A PR addressing the issue has already been submitted and merged but unfortunately the fix hasn't been released yet. It can be expected 在 .NET Core 2.2.0 中。
在那之前你对此无能为力,正如 this discussion 末尾所说的那样。