在泛型集合中存储后,重载和多态行为会发生变化

时间:2013-09-26 17:41:34

标签: c# mono polymorphism overloading

我正在从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使用 在foo中正确重载方法Specific

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

显然,在这个示例中,我可以将s2转换为Generic以获得预期结果。 似乎.net(或我使用的单声道)取决于运行时绑定 它决定了使用什么方法。

在我们的生产系统中,铸造是不可取的。

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

提前致谢,

克里斯

2 个答案:

答案 0 :(得分:2)

这是因为您没有重载方法,也没有将基本方法声明为虚拟,因此不使用虚方法分派。这意味着方法调用将在编译时绑定,因此用于调用方法的类型的引用将确定调用哪个方法,而不是引用对象的类型< / em>的

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

要解决此问题,请将Generic中的方法声明为virtual

public virtual String foo(M n)

并在Specific

中覆盖它
public override String foo(M n)

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

请注意,覆盖不能更改被覆盖方法的参数类型,因此您无法在foo()中声明Specific以接受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
}

答案 1 :(得分:2)

如果您想致电foo Specific,则必须投票:

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)";
    }
}

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