2013-02-18 1 views
1

Я работал с 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"); 
    } 
} 
+0

Это поведение, в котором вы определяете открытый метод e.x. Учитывать не поведение по умолчанию .net при написании того же кода в C#? Если это так, вам нужно будет переопределить для IList.Count и IDictionary.Count отдельно. Я бы предположил, что при этом вы не должны иметь конфликт имен, потому что IDictionary не наследует IList, поэтому реализация будет другой. – rpgmaker

+0

Если вы делаете это в C#, он не добавляет DefineMethodOverride. Если вы реализуете их отдельно, я не вижу способа сделать их общедоступными. Тем не менее, есть и другие языки для рассмотрения, некоторые из которых могут переопределять методы и давать им другое имя (которое действительно!) – atlaste

+0

Как указано в kvb, вам нужно будет использовать IList.Count и IDictionary.Count. Вы сами определяете этот метод как общедоступный, но вы будете специфицировать methodInfo для IList.Count и IDictionary.Count, которые являются частными. Поступая таким образом, вы получите доступ только к Count для IList, когда объект передается в IList, поскольку переопределение является явным, а не подразумеваемым. – rpgmaker

ответ

2

Если у вас есть несколько методов с тем же именем и сигнатурой, то ваш тип недействителен. Если вы хотите явно реализовать интерфейс, то обычно вы будете использовать частные методы, которые имеют разные имена (например, компилятор C# использует что-то вроде IList.Count вместо Count).

UPDATE

После обновления вашего я теперь вижу, что у меня был диагноз несколько назад. Если класс объявлен как реализация интерфейса, и существует метод интерфейса без переопределения соответствующего метода, CLR будет искать метод с тем же именем и сигнатурой. Невозможно сказать CLR не делать этого (кроме предоставления другого определенного переопределения для этого метода интерфейса), и один и тот же конкретный метод может переопределить несколько методов интерфейса, по дизайну. Точно так же было бы прекрасно, если бы ваш конкретный метод назывался Third и явно перекрывал как First, так и Second.

UPDATE 2

Я попытаюсь ответить на ваш другой вопрос, но «почему» всегда трудно. Прежде всего, добавление метода к интерфейсу является серьезным нарушением изменений и в принципе никогда не произойдет - если вы добавите новый метод в свой интерфейс, тогда вы ожидаете, что все классы реализуют этот интерфейс (который может быть разработан сторонними организациями, если ваш интерфейс является общедоступным) будут ломаться, так как они заявляют о реализации интерфейса, но не имеют метода.

Кажется, что недостаток в возможности переопределить несколько методов интерфейса с помощью конкретной конкретной реализации. Как правило, эти методы были бы с разных интерфейсов, поэтому это было бы удобным способом избежать создания нескольких реализаций, которые идентичны (предполагая, что разные методы интерфейса имеют идентичную семантику, так что их переопределение через один метод имеет смысл). Аналогичным образом, CLR удобнее искать методы по имени + подписи, а не требовать явного сопоставления. Ваш случай - это, по сути, очень странный случай с угловым случаем этих более общих механизмов, когда один метод использует один и тот же интерфейс, один раз через явное переопределение и один раз через поиск по умолчанию. Это очень странно, похоже, что CLR явно не ищет конкретного сценария, особенно учитывая, что было бы маловероятно (или невозможно) генерировать из источника C#.

+0

Хотя у вас есть точка, у меня возникают проблемы с этим ответом. Там больше, чем C#, чтобы рассмотреть здесь ... И в IL это совершенно законно, чтобы дать вашей перегрузке другое имя в IL, например. вы можете назвать IList.Count еще что-то вроде -well-IsReadOnly с DefineMethodOverride, который сообщает .NET, что это переопределение IList.Count :-) Если кто-то решит добавить другое свойство в IList (IsReadOnly), вы ожидаете, что код не будет компилировать - но на самом деле это будет! Если используется память, вы даже можете определить несколько методов с тем же именем и сигнатурой в IL, если они имеют разные типы возвращаемых данных. – atlaste

+0

Я говорил об уровне IL, а не C#: у вас не может быть два метода с тем же именем и сигнатурой в типе, или тип неверен (и возвращает тип count как часть подписи). Вы правы, что методы могут иметь произвольные имена и по-прежнему переопределять методы интерфейса с разными именами. В самом деле, это то, что я предлагаю вам реализовать (назовите ваш переопределить «IList.Count» вместо «Count»). – kvb

+0

Ах да, это правильно, мы согласны с этим. Это означает, что моя проблема в том, что если у меня есть 'IList :: Count' и реализовать это как MyType :: IsReadOnly с явным' DefineMethodOverride', я не понимаю, почему он также неявно переопределяет 'IList :: IsReadOnly', - В конце концов, это не то, что я указал. (при условии, что он имеет ту же самую подпись). Таким образом, очевидно, что 'DefineMethodOverride' только _includes_ переопределяет, но не _exclude_ все остальные, что я нахожу в значительной степени спорным поведением по причине того, что это может сломать материал, если интерфейс изменится. Так почему это происходит? – atlaste

 Смежные вопросы

  • Нет связанных вопросов^_^