Я работал с Reflection.Emit в течение длительного времени, но на этот раз это просто не имеет смысла ... Где-то в моей программе я реализую интерфейсы с использованием emit. Например .:Почему реализация интерфейса в Emit с явной перегрузкой ведет себя по-разному для публичных и непубличных?
typeBuilder.AddInterfaceImplementation(intf);
Поскольку я реализую несколько интерфейсов и интерфейсов может наследовать от других интерфейсов, I-де-дублирующие методы/интерфейсов. (Хотя это не связано здесь, я собираюсь использовать некоторые известные интерфейсы для моих примеров). Например, если я реализую как IList, так и IDictionary, они реализуют ICollection, и я только реализую ICollection один раз.
После этого я начинаю добавлять методы к typeBuilder, используя результирующий список методов и интерфейса. Ничего необычного, просто:
MethodBuilder mb = typeBuilder.DefineMethod(
name,
MethodAttributes.HideBySig | MethodAttributes.NewSlot | MethodAttributes.Virtual |
MethodAttributes.Final | specialAttributes, CallingConventions.HasThis,
returnType,
parameterTypes);
// [...] emit code that doesn't really matter here
typeBuilder.DefineMethodOverride(mb, baseMethodFromInterface);
Обратите внимание, что я явно определяю переопределение метода. Я делаю это, потому что имена могут столкнуться, f.ex. в приведенном выше примере как IList, так и ICollection выставляют графа getter (name = get_Count), что приведет к столкновению имен.
Предположим, теперь я использую имя «Count» при создании методов. Как я заметил ранее, существует пара интерфейсов, которые производятся от IList, которые реализуют это свойство. Я запутался в том, что, очевидно, теперь «Count» неявно наследует и от других методов Count, даже если я не определяю его как Override ... но только если я выставил свойство как общедоступное. (например, specialAttributes = MethodAttributes.Public). Что происходит, что PEVerify выдаст ошибку, но код будет работать нормально:
[IL]: Error: [c:\tmp\emit\Test.dll : Test::get_Count][offset 0x00000012] Method is not visible.
Чтобы решить эту проблему, я попытался изменить specialAttributes = MethodAttributes.Private - но из-за незначительной ошибки, я не имею реализованы все функции подсчета явно (используя DefineMethodOverride). Как ни странно, CreateType теперь говорит мне, что «Count [...] не имеет реализации». - например. он не может найти метод Count, который действует как переопределение.
Однако, поскольку я использую DefineMethodOverride, мне интересно, почему это сработало в первую очередь? Другими словами: «Частные» ошибки имеют смысл, тот факт, что он работал при использовании общедоступных методов, не является ИМО.
Так что для моих вопросов: почему .NET неявно переопределяет общедоступные методы с тем же именем, даже если вы явно определяют переопределение в качестве переопределения для другого метода (это звучит для меня как ошибка в .NET ...)? Почему это работает? И почему PEVerify дает ошибку, когда вы публикуете методы как общедоступные?
обновление
Видимо ошибка PEVerify не имеет никакого отношения: после того, как делает все приватным и реализация всех методов явных, PEVerify еще дает ту же ошибку. Ошибка связана с вызовом неправильный метод, например .:
// Incorrect: attempt to call a private member -> PEVerify error
callvirt instance void [mscorlib]System.Collections.Generic.Dictionary`2<string, int32>::System.Collections.IDictionary.Remove(object)
// Correct: call the interface using a vtable lookup
callvirt instance void [mscorlib]System.Collections.IList::Remove(object)
Тем не менее, это всего лишь разъезд, остаются вопросы.
Update +1
Я сделал то, что я считаю минимальный TestCase. Это в основном генерирует DLL, которую вы должны проверить с помощью своего любимого инструмента. Обратите внимание, что я использую только один метод здесь, а не 2 (!). Второй метод переопределяется «автоматически», хотя я явно говорю.NET, что метод реализует первый метод.
public interface IFoo
{
int First();
int Second();
}
public class FooGenerator
{
static void Main(string[] args)
{
CreateClass();
}
public static void CreateClass()
{
// Create assembly
var assemblyName = new AssemblyName("test_emit.dll");
var assemblyBuilder =
AppDomain.CurrentDomain.DefineDynamicAssembly(assemblyName,
AssemblyBuilderAccess.RunAndSave, @"c:\tmp");
// Create module
var moduleBuilder = assemblyBuilder.DefineDynamicModule("test_emit", "test_emit.dll", false);
// Create type : IFoo
var typeBuilder = moduleBuilder.DefineType("TestClass", TypeAttributes.Public);
typeBuilder.AddInterfaceImplementation(typeof(IFoo));
ConstructorBuilder constructorBuilder = typeBuilder.DefineConstructor(
MethodAttributes.Public | MethodAttributes.HideBySig |
MethodAttributes.SpecialName | MethodAttributes.RTSpecialName,
CallingConventions.HasThis, Type.EmptyTypes);
// Generate the constructor IL.
ILGenerator gen = constructorBuilder.GetILGenerator();
// The constructor calls the constructor of Object
gen.Emit(OpCodes.Ldarg_0);
gen.Emit(OpCodes.Call, typeof(object).GetConstructor(
BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance, Type.DefaultBinder, Type.EmptyTypes, null));
gen.Emit(OpCodes.Ret);
// Add the 'Second' method
var mb = typeBuilder.DefineMethod("Second",
MethodAttributes.HideBySig | MethodAttributes.NewSlot | MethodAttributes.Virtual |
MethodAttributes.Final | MethodAttributes.Public, CallingConventions.HasThis,
typeof(int), Type.EmptyTypes);
// Implement
gen = mb.GetILGenerator();
gen.Emit(OpCodes.Ldc_I4_1);
gen.Emit(OpCodes.Ret);
typeBuilder.DefineMethodOverride(mb, typeof(IFoo).GetMethod("First"));
typeBuilder.CreateType();
assemblyBuilder.Save("test_emit.dll");
}
}
Это поведение, в котором вы определяете открытый метод e.x. Учитывать не поведение по умолчанию .net при написании того же кода в C#? Если это так, вам нужно будет переопределить для IList.Count и IDictionary.Count отдельно. Я бы предположил, что при этом вы не должны иметь конфликт имен, потому что IDictionary не наследует IList, поэтому реализация будет другой. – rpgmaker
Если вы делаете это в C#, он не добавляет DefineMethodOverride. Если вы реализуете их отдельно, я не вижу способа сделать их общедоступными. Тем не менее, есть и другие языки для рассмотрения, некоторые из которых могут переопределять методы и давать им другое имя (которое действительно!) – atlaste
Как указано в kvb, вам нужно будет использовать IList.Count и IDictionary.Count. Вы сами определяете этот метод как общедоступный, но вы будете специфицировать methodInfo для IList.Count и IDictionary.Count, которые являются частными. Поступая таким образом, вы получите доступ только к Count для IList, когда объект передается в IList, поскольку переопределение является явным, а не подразумеваемым. – rpgmaker