Skip to main content
 首页 » 编程设计

c#之存储在通用集合中后重载和多态行为发生变化

2024年02月13日35dudu

我正在用 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 
}