c# - 生成正确的 IL 以在泛型类上使用带有新 "in"修饰符的参数调用虚拟方法

标签 c# .net-core intermediate-language

我正在写一个 serialization/deserialization framework .NET Core 2.1 中新的 System.IO.Pipelines 包。在生成 IL 以调用带有泛型类上新“in”修饰符的参数的虚拟方法时,我遇到了一个问题。这基本上是我尝试调用的方法签名:

public virtual T DoSomething(in ReadOnlySpan<byte> memory, T o);

如果我取消 virtual 修饰符,我的代码运行良好。添加 virtual 修饰符后,我在尝试调用生成的代码时遇到 MethodNotFound 异常。我还注意到,如果我不在方法参数的任何地方使用 in 修饰符,它仍然可以正常工作。如果我从类中删除通用参数(并保留 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 中找到实际的真实世界代码.检查benchmark project出去了解正在发生的事情/它是如何使用的。

最佳答案

这是 CoreCLR 中的一个已知错误: https://github.com/dotnet/corefx/issues/29254

A PR addressing the issue已经提交并合并,但遗憾的是修复尚未发布。它can be expected在 .NET Core 2.2.0 中。

在那之前你对此无能为力,正如 this discussion 末尾所说的那样.

关于c# - 生成正确的 IL 以在泛型类上使用带有新 "in"修饰符的参数调用虚拟方法,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/52321428/

相关文章:

c# - 有没有办法知道请求是使用 ASP.NET MVC 中的 Redirect() 方法发出的

.net-core - EF Core Migration Add - 使用意外的 DropForeignKey、DropUniqueConstraint、DropColumn 语句创建迁移

.net - F#解释器(fsi.exe)是否也像F#编译器(fsc.exe)一样生成中间语言代码?

c# - 不存储引用时更大的 .maxstack?

C# CefSharp DevTool 的关闭按钮不起作用

c# - 使用 JSON.net 处理(反)序列化主数据

c# - DataGrid 的 DataGridComboBoxColumn 绑定(bind)到 Dictionary

javascript - React Native 时间线 ListView

c# - Entity Framework EF.Functions.Like 与 string.Contains