1

我正在用 C#(闪存到统一)从 ActionScript 移植/重写我们的生产系统。

我遇到了 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使用正确的重载方法fooSpecific

在我推入s堆栈并再次将其弹出后,第二次中的相同函数调用Console.Write意外地使用了foo from Generic

显然,在这个例子中,我可以s2在这里转换Generic以获得预期的结果。似乎.net(或我使用的单声道)取决于确定使用哪种方法的运行时绑定。

在我们的生产系统中,铸造是没有选择的。

有没有办法在第二次通话中获得预期的结果?

提前致谢,

克里斯

4

2 回答 2

2

发生这种情况是因为您没有重载方法,并且基方法也没有声明为虚拟的,因此不使用虚拟方法分派。这意味着方法调用将在编译时绑定,因此用于调用方法的引用类型将决定调用哪个方法,而不是被引用对象的类型

foo()您应该在声明的行上收到警告,Specific因为它隐藏了父类型的成员并且您没有指定new. 注意警告!

要解决此问题,请将方法声明Genericvirtual

public virtual String foo(M n)

并覆盖它Specific

public override String foo(M n)

在这种情况下,调用将在运行时绑定到此方法的特定覆盖,使用调用该方法的实际对象的 vtable。

请注意,覆盖不能更改被覆盖方法的参数类型,因此您不能声明foo()inSpecific接受N对象。但是,如果需要,您可以指定额外的重载。重载可能需要查看传递对象的foo(M)类型以确定如何处理它。(或者,更好的是,foo(M) 不要在意它是否通过了 anM或 an 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
}
于 2013-09-26T17:45:27.957 回答
2

如果你想打电话fooSpecific你必须投:

var s2 = (Specific)stack.Peek();

这会给你想要的结果。

如果你让你的Generic类通用

public class Generic<T> where T : M
{
    public virtual String foo(T n)
    {
        return "I don't want this generic foo for M";
    }
}

然后你可以这样做:

public class Specific : Generic<N>
{
    public override string foo(N n)
    {
        return "I want this specific foo for N (child of M)";
    }
}

但是你需要更多的解决方法。

于 2013-09-26T17:48:38.400 回答