代码之家  ›  专栏  ›  技术社区  ›  smoyer

生成正确的IL,以便在泛型类上使用带有新“in”修饰符的参数调用虚拟方法

  •  4
  • smoyer  · 技术社区  · 6 年前

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

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

    如果我去掉虚修饰符,我的代码运行良好。一旦我添加虚拟修饰符,我就得到一个 MethodNotFound 尝试调用生成的代码时出现异常。我也注意到如果我不使用 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 去弄清楚发生了什么事/是怎么用的。

    1 回复  |  直到 6 年前
        1
  •  1
  •   Adam Simon    6 年前

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

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

    在那之前,你们不能对此做太多的事情,因为这也是在本世纪末所说的 this discussion .