虽然提出的案例很明显,但我偶然发现了一些涉及动态转换的奇怪行为。看看下面的程序:
class Base { }
class Child : Base { }
class Program
{
static void Main(string[] args)
{
Base node = GetChild();
Test((dynamic) node);
node = GetBase();
Test((dynamic) node);
}
static Child GetChild()
{
return null;
}
static Base GetBase()
{
return null;
}
// Guess how many times each method is called..
static void Test(Base node)
{
// Nope!
}
static void Test(Child child)
{
// It's this one twice.
}
}
使用 .NET 反射器检查代码会导致崩溃(不明确匹配),但 dotPeek 提供了对生成的 IL 的仔细查看(在此处反编译):
private static void Main(string[] args)
{
Base base1 = (Base) Program.GetChild();
if (Program.<Main>o__SiteContainer0.<>p__Site1 == null)
Program.<Main>o__SiteContainer0.<>p__Site1 = CallSite<Action<CallSite, Type, object>>.Create(Binder.InvokeMember(CSharpBinderFlags.ResultDiscarded, "Test", (IEnumerable<Type>) null, typeof (Program), (IEnumerable<CSharpArgumentInfo>) new CSharpArgumentInfo[2]
{
CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.UseCompileTimeType | CSharpArgumentInfoFlags.IsStaticType, (string) null),
CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, (string) null)
}));
Program.<Main>o__SiteContainer0.<>p__Site1.Target((CallSite) Program.<Main>o__SiteContainer0.<>p__Site1, typeof (Program), (object) base1);
Base base2 = Program.GetBase();
if (Program.<Main>o__SiteContainer0.<>p__Site2 == null)
Program.<Main>o__SiteContainer0.<>p__Site2 = CallSite<Action<CallSite, Type, object>>.Create(Binder.InvokeMember(CSharpBinderFlags.ResultDiscarded, "Test", (IEnumerable<Type>) null, typeof (Program), (IEnumerable<CSharpArgumentInfo>) new CSharpArgumentInfo[2]
{
CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.UseCompileTimeType | CSharpArgumentInfoFlags.IsStaticType, (string) null),
CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, (string) null)
}));
Program.<Main>o__SiteContainer0.<>p__Site2.Target((CallSite) Program.<Main>o__SiteContainer0.<>p__Site2, typeof (Program), (object) base2);
}
我有点理解动态调用站点的构造,以及使用两个变量(base1 和 base2)而源仅使用一个变量这一事实可能是由于优化。但是,那个 base2 变量与 Child 类型无关!它被声明为 Base 类型的变量,并从具有 Base 原型的函数中使用 null 进行初始化。
添加另一个类和处理程序方法会通过引发运行时异常来实现您可能期望或可能不期望的结果“以下方法或属性之间的调用不明确:'Program.Test(Child)' 和 'Program.Test(AnotherChild)'”。在第一个和第二个动态转换中都会引发此异常,这是可以预料的,因为它是相似的代码。
class AnotherChild : Base { }
static void Test(AnotherChild child)
{
// Ambiguous between AnotherChild and Child -> Runtime Exception
}
换句话说,似乎无论类型如何,对空对象的动态转换都会自下而上遍历继承树,并在找到该级别上的多个类型时放弃。作为上述构造,让 AnotherChild 继承自Child 确实调用了 AnotherChild 处理程序。
class AnotherChild : Child { }
老实说,我认为对空对象进行动态转换应该是非法的,如果每次都抛出会更好。当您进行动态转换时,您可能还会使用方法重载;这似乎是一个正在酝酿中的无声杀手。