私はReflection.Emitを長い間使用してきましたが、今回は意味がありません...プログラムのどこかで、emitを使用してインターフェイスを実装しています。例えば:
typeBuilder.AddInterfaceImplementation(intf);
私は複数のインターフェースを実装しており、インターフェースは他のインターフェースから継承できるため、メソッド/インターフェースの重複を排除します。(ここでは関係ありませんが、例としていくつかのよく知られたインターフェイスを使用します)。たとえば、IListとIDictionaryの両方を実装する場合、両方ともICollectionを実装し、ICollectionを1回だけ実装します。
その後、結果のメソッドとインターフェイスのリストを使用して、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の両方がCount getter(name = get_Count)を公開しているため、名前が衝突します。
ここで、メソッドの生成中に「Count」という名前を使用するとします。前に気付いたように、このプロパティを実装するIListから派生したインターフェイスがいくつかあります。私が混乱しているのは、「Count」が他のCountメソッドからも暗黙的に継承されていることです。オーバーライドとして定義していなくても、プロパティをパブリックとして公開している場合に限ります。(例:specialAttributes = MethodAttributes.Public)。何が起こるかというと、PEVerifyはエラーを出しますが、コードは問題なく実行されます。
[IL]: Error: [c:\tmp\emit\Test.dll : Test::get_Count][offset 0x00000012] Method is not visible.
これを解決するために、specialAttributes = MethodAttributes.Privateを変更しようとしましたが、マイナーなバグのため、すべてのCountを明示的に実装していません(DefineMethodOverrideを使用)。不思議なことに、CreateTypeは、「Count[...]には実装がありません」と表示するようになりました。-たとえば、オーバーライドとして機能するCountのメソッドが見つかりません。
ただし、DefineMethodOverrideを使用しているので、そもそもなぜそれが機能したのか疑問に思います。言い換えると、「プライベート」エラーは理にかなっています。パブリックメソッドを使用したときに機能したという事実はIMOではありません。
だから私の質問のために:オーバーライドを別のメソッドのオーバーライドとして明示的に定義したとしても、なぜ.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)
それでも、これは単なる副産物であり、疑問が残ります。
+1を更新
最小限のテストケースと考えるものを作成しました。これにより、基本的に、お気に入りのツールで検査する必要のあるDLLが生成されます。ここでは、2つではなく1つのメソッドのみを実装していることに注意してください(!)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");
}
}