我正在用 C# 从 ActionScript 移植/重写我们的生产系统(从 flash 到 unity)。
我遇到了 C# 的奇怪行为,这使我无法实现我们的设计。
下面的代码重现了这个问题:
using System;
using System.Collections.Generic;
namespace DotNetTest
{
class MainClass
{
public static void Main (string[] args)
{
// first, expected behaviour
Specific s = new Specific ();
N data = new N ();
Console.WriteLine (s.foo(data));
// now I'm going to push s on a stack
Stack<Generic> stack = new Stack<Generic> ();
stack.Push (s);
// and use it again: different behaviour !
var s2 = stack.Peek ();
Console.WriteLine (s2.foo (data));
}
}
public class M
{
}
public class N : M
{
}
public class Generic
{
public String foo(M n)
{
return "I don't want this generic foo for M";
}
}
public class Specific : Generic
{
public String foo(N m)
{
return "I want this specific foo for N (child of M)";
}
}
}
当我运行这段代码时,我得到以下结果
I want this specific foo for N (child of M)
I don't want this generic foo for M
在 Main 中,我有一个变量 s。第一个 Console.Write 使用 在 Specific 中正确重载方法 foo。
在我将 s 压入堆栈并再次将其弹出后,相同的 第二个 Console.Write 中的函数调用意外地使用了 foo 来自 Generic。
显然,在此示例中,我可以将此处的 s2 转换为 Generic 以获得预期的结果。 似乎.net(或我使用的单声道)取决于运行时绑定(bind) 这决定了使用什么方法。
在我们的生产系统中,类型转换是不可取的。
有没有办法在第二次调用时得到预期的结果?
提前致谢
克里斯
请您参考如下方法:
发生这种情况是因为您没有重载该方法,也没有将基方法声明为虚拟,因此不使用虚拟方法分派(dispatch)。这意味着方法调用将在编译时绑定(bind),因此用于调用方法的引用类型将决定调用哪个方法,而不是引用对象的类型.
您应该在 Specific 中声明 foo() 的行上收到警告,因为它从父类型中隐藏了成员并且您没有指定 new。注意警告!
要解决此问题,请将 Generic 中的方法声明为 virtual:
public virtual String foo(M n)
并在 Specific 中覆盖它:
public override String foo(M n)
在这种情况下,调用将在运行时绑定(bind)到此方法的特定覆盖,使用调用该方法的实际对象的 vtable。
请注意,覆盖不能更改被覆盖方法的参数类型,因此您不能在 Specific 中声明 foo() 来接受 N 对象。但是,如果需要,您可以指定一个额外的重载。 foo(M) 重载可能需要查看传递的对象的类型以确定如何处理它。 (或者,更好的是,让 foo(M) 不关心它是通过 M 还是 N .)
覆盖的可能实现应该只接受 N(尽管这有点糟糕的设计):
public override String foo(M m)
{
if (m == null) { throw new ArgumentNullException("m"); }
N n = m as N;
if (n == null) { throw new ArgumentException("Must be an N instance.", "m"); }
return foo(n);
}
public virtual String foo(N n)
{
// Use the n variable
}
