Прежде чем начать, это мой первый вопрос о SO.Таким образом, могут быть какие-либо ошибки или недостаток информации о проблеме.Пожалуйста, дайте мне знать, если мне нужно что-то исправить.Спасибо.
Я создаю класс, который реализует интерфейс, содержащий метод с TypeBuilder
.После реализации этого метода с ILGenerator
, затем я вызываю TypeBuilder.CreateType()
, и в обычном случае все идет хорошо.Но если метод содержит какой-либо параметр с модификатором in
, также известным как ссылка только для чтения для типов значений, TypeBuilder.CreateType()
выдает TypeLoadException("Method 'SomeMethod' ... does not have an implementation.")
.
В отличие от обычногоВ случае TypeLoadException
, когда реализованный метод с такой же сигнатурой, объявленной в интерфейсе (интерфейсах), не существует, эта проблема возникает только тогда, когда метод содержит in
параметров, даже если сигнатуры одинаковы.Когда я удаляю или изменяю модификатор in
на ref
или out
, TypeBuilder.CreateType()
успешно распознает сгенерированный метод как реализацию одного из объявленных в интерфейсе, и тип создается нормально.
Вотполностью компилируемый пример:
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 = namedPropArgs.Select(arg => (FieldInfo)arg.MemberInfo).ToArray();
var namedFieldValues = namedPropArgs.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.InAttribute
и CompilerServices.IsReadOnlyAttribute
.Но когда я генерирую метод без реализации интерфейса (это обычно происходит успешно, так как не требуется сопоставление подписи) , in
параметр сгенерированного метода имеет только один пользовательский атрибут, InAttribute
.Поэтому я скопировал все пользовательские атрибуты параметров из интерфейса, но все еще вызывается TypeLoadException.
Я проверял это на .NET Framework 4.6.1
и .NET Core 2.2
с C# 7.2 and 7.3
.И все среды дали мне одно и то же исключение.Я использую Visual Studio 2017 для Windows.
Есть что-то, что я пропустил, или какой-нибудь обходной путь?
Спасибо за любую помощь заранее.