【发布时间】:2013-02-03 12:40:55
【问题描述】:
我已经使用 Reflection.Emit 很长时间了,但这次它没有任何意义......在我的程序的某个地方,我正在使用 emit 实现接口。例如:
typeBuilder.AddInterfaceImplementation(intf);
因为我正在实现多个接口并且接口可以从其他接口继承,所以我对方法/接口进行了去重。 (虽然这里不相关,但我将在我的示例中使用一些众所周知的接口)。例如,如果我同时实现 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 都公开了一个 Count 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 - 但由于一个小错误,我没有显式地实现所有 Count 事物(使用 DefineMethodOverride)。奇怪的是,CreateType 现在告诉我“Count [...] 没有实现”。 - 例如它找不到充当覆盖的 Count 方法。
但是,由于我使用的是 DefineMethodOverride,我想知道为什么它首先会起作用?换句话说:“私人”错误是有道理的,它在使用公共方法时起作用的事实在 IMO 中不起作用。
所以对于我的问题:即使您显式将覆盖定义为另一个方法的覆盖,为什么 .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,您应该使用您喜欢的工具对其进行检查。请注意,我在这里只实现 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");
}
}
【问题讨论】:
-
是您定义公共方法的行为,例如。在 C# 中编写相同的代码时,不计算 .net 的默认行为吗?如果是这样,那么您将需要分别覆盖 IList.Count 和 IDictionary.Count。我猜这样做,你不应该有名称冲突,因为 IDictionary 不继承 IList 所以实现会有所不同。
-
如果您在 C# 中执行此操作,则不会添加 DefineMethodOverride 。如果您单独实施它们,我看不到将它们公开的方法。尽管如此,还有其他语言需要考虑,其中一些可能能够覆盖方法并给它们一个不同的名称(这是有效的!)
-
如 kvb 所述,您将不得不使用 IList.Count 和 IDictionary.Count。您将方法本身定义为公共的,但您将指定 IList.Count 和 IDictionary.Count 的 methodInfo,它们是私有的。通过这样做,您将只能在将对象强制转换为 IList 时访问 IList 的 Count,因为覆盖是显式的而不是隐式的。
-
是的,我知道 C# 语法,但这可能不是一个正当的理由。另见msdn.microsoft.com/en-us/library/…:它明确声明“使用不同的名称”,没有限制。在 .NET 中还需要考虑 Javascript、Python、Ruby、VB 和许多其他语言......如果这个约束不适用于所有可能的语言,我不会感到惊讶。
-
场景完全有效。您定义了第二种方法,并为第一种方法调用了 DefineOverride。第二个方法应该自动覆盖接口的第二个,第二个方法定义将用于第一个方法覆盖。因此,您在第二次发出的任何代码都将首先反映。这种情况仅是有效的,因为返回类型和参数类型是相同的。这不适用于返回类型和参数类型不同的场景
标签: c# .net reflection reflection.emit