【问题标题】:How method hiding works in C#? (Part Two)方法隐藏如何在 C# 中工作? (第二部分)
【发布时间】:2010-10-17 03:55:49
【问题描述】:

以下程序打印

A:C(A,B)
B:C(A,B)

(应该如此)

public interface I
{
    string A();
}

public class C : I
{
    public string A()
    {
        return "A";
    }

    public string B()
    {
        return "B";
    }
}

public class A
{
    public virtual void Print(C c)
    {
        Console.WriteLine("A:C(" + c.A() + "," + c.B() + ")");
    }
}

public class B : A
{
    public new void Print(C c)
    {
        Console.WriteLine("B:C(" + c.A() + "," + c.B() + ")");
    }

    public void Print(I i)
    {
        Console.WriteLine("B:I(" + i.A() + ")");
    }
}

class Program
{
    public static void Main(string[] args)
    {
        A a = new A();
        B b = new B();
        C c = new C();
        a.Print(c);
        b.Print(c);
    }
}

但是,如果我在 B 类中将关键字“new”更改为“override”,如下所示:

    public override void Print(C c)

突然程序开始打印:

A:C(A,B)
B:I(A)

为什么?

【问题讨论】:

    标签: c# inheritance compiler-construction method-hiding


    【解决方案1】:

    这至少是一个关于方法重载如何在C#中工作的问题。我认为您在这里强调了一个有趣的情况...

    在第一种情况下(在方法上使用new 关键字),编译器决定使用带有C 类型参数的Print 方法重载,因为它的类型与传递的参数的类型完全相同(即不需要隐式转换),而如果编译器选择采用 I 类型参数的Print 方法,则需要对接口 I 进行隐式转换 - 换句话说,它选择更“明显”的方法重载。

    在第二种情况下(在方法上使用 override 关键字),编译器决定使用带有类型 I 参数的 Print 的重载,因为尽管您在 B 类中覆盖了 Print(C c) 方法重载, 它在父类 A 中有效定义,使得 Print(I i) 方法重载实际上是最高级别的重载,因此是最直接的重载,即编译器找到的第一个。

    希望这能帮助你理解。让我知道如果我需要进一步澄清任何观点......

    注意:如果我说编译器做这些事情是错误的,那么请纠正我,尽管为了争论,无论是编译器还是 CLR/JIT 似乎都没有什么区别。

    【讨论】:

      【解决方案2】:

      这是一个很好的问题。
      所有答案都可以在这里找到: http://msdn.microsoft.com/en-us/library/6fawty39(VS.80).aspx

      它的要点是这样的:

      ...C# 编译器将首先尝试使 与版本兼容的调用 的 [functionName] 最初声明于 [派生类]。覆盖方法不是 被认为是在一个类上声明的, 它们是 a 的新实现 在基类上声明的方法。仅有的 如果 C# 编译器无法匹配 对原始方法的方法调用 [派生类] 是否会尝试匹配调用 到具有相同的覆盖方法 名称和兼容参数。

      因此,因为您在派生类上有一个与参数“c”匹配的新方法 Print(I i)(因为 c 实现了 I),所以该方法优先于“覆盖”方法。

      当你将方法标记为“new”时,它们都被认为是在派生类上实现的,并且 Print(C c) 方法与参数“c”更匹配,因此具有优先权。

      【讨论】:

        【解决方案3】:

        这与如何解决重载方法有关。

        实际上(有些简化),在这种情况下,编译器首先查看表达式 (B) 的声明类型,然后寻找候选方法首先在该类型中声明。如果有任何合适的方法(即所有参数都可以转换为方法的参数类型),那么它不会查看任何父类型。这意味着,如果派生类型中有任何“新声明”的适当方法,则初始声明位于父类型中的重写方法不会得到查看。

        这是一个稍微简单的例子:

        using System;
        
        class Base
        {
            public virtual void Foo(int x)
            {
                Console.WriteLine("Base.Foo(int)");
            }
        }
        
        class Derived : Base
        {
            public override void Foo(int x)
            {
                Console.WriteLine("Derived.Foo(int)");
            }
        
            public void Foo(double d)
            {
                Console.WriteLine("Derived.Foo(double)");
            }
        }
        
        class Test
        {
            static void Main()
            {
                Derived d = new Derived();
                d.Foo(10);
            }
        }
        

        这将打印Derived.Foo(double) - 即使编译器知道有一个匹配方法,其参数类型为int,并且参数类型为int,并且从intint 的转换是“比从intdouble 的转换更好”,事实上只有Foo(double) 方法最初在Derived声明意味着编译器忽略Foo(int)

        这对 IMO 来说是非常令人惊讶的。如果Derived 没有覆盖Foo,我可以明白为什么会出现这种情况——否则在基类中引入一个新的、更具体的方法可能会意外地改变行为——但显然Derived 在这里知道 关于Base.Foo(int),因为它覆盖了它。这是我认为 C# 设计者做出错误决定的(相对少数)点之一。

        【讨论】:

        • 此外,如果您将d 作为参数传递给需要Base 的方法,您将得到Derived.Foo(int)(与在Main() 中写入(d as Base).Foo(10); 相同)。虽然很清楚它是如何工作的,但我觉得这很臭,因此在所有情况下都避免隐藏方法。
        【解决方案4】:

        好的,所以

            public new void Print(C c)
            {
                Console.WriteLine("B:C(" + c.A() + "," + c.B() + ")");
            }
        
            public void Print(I i)
            {
                Console.WriteLine("B:I(" + i.A() + ")");
            }
        

        这声明了一个新的打印方法。现在因为 B 继承自 A,所以您只需调用新方法两次。当您覆盖该方法时,这会在您调用 A 时更改方法签名,但是当您调用 B 签名时,它有自己的方法签名。

        我不确定我是否在解释清楚但很好的问题。

        使用新的:

        A 和 B 获得相同的打印方法实现。

        使用覆盖:

        A 与 B 的方法签名不同,因为您没有仅在 A 中更改 B 中的方法签名。

        使用新的它基本上忽略了这一点:

            public void Print(I i)
            {
                Console.WriteLine("B:I(" + i.A() + ")");
            }
        

        【讨论】:

          猜你喜欢
          • 2010-10-17
          • 2021-11-22
          • 1970-01-01
          • 2019-06-15
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2021-01-09
          • 1970-01-01
          相关资源
          最近更新 更多