C#允许在虚拟方法上使用new关键字的用例是什么?

5
作为一名主要从事Java开发的开发者,有一天我偶然使用了new关键字而不是override,结果让我有些惊讶。
看起来new关键字会在继承树中的那个级别上移除方法的“虚拟性”,因此,在将子类实例向下转换为父类并调用方法时,将不会解析到子类中的方法实现。
这种行为有哪些实际用例?
澄清一点:我理解当父类不是虚拟的时候使用new的用法。我更好奇的是编译器为什么允许new和virtual组合使用。
以下示例说明了差异:
using System;

public class FooBar
{
    public virtual void AAA()
    {
        Console.WriteLine("FooBar:AAA");
    }

    public virtual void CCC()
    {
        Console.WriteLine("FooBar:CCC");
    }
}

public class Bar : FooBar
{
    public new void AAA()
    {
        Console.WriteLine("Bar:AAA");
    }

    public override void CCC()
    {
        Console.WriteLine("Bar:CCC");
    }
}

public class TestClass
{
    public static void Main()
    {
        FooBar a = new Bar();
        Bar b = new Bar();
        Console.WriteLine("Calling FooBar:AAA");
        a.AAA();
        Console.WriteLine("Calling FooBar:CCC");
        a.CCC();
        Console.WriteLine("Calling Bar:AAA");
        b.AAA();
        Console.WriteLine("Calling Bar:CCC");
        b.CCC();
        Console.ReadLine(); 
    }
}

这将产生以下输出:
Calling FooBar:AAA
FooBar:AAA
Calling FooBar:CCC
Bar:CCC
Calling Bar:AAA
Bar:AAA
Calling Bar:CCC
Bar:CCC
3个回答

15

应用场景:

  • 今天,您使用第三方库并从类Fruit派生出类Banana
  • 您在Banana中实现了一个名为Peel的方法。但在Fruit中没有Peel方法。
  • 明天,第三方发布了一个新版本的库,其中包括一个虚拟的Fruit.Peel方法。
  • 明天重新编译代码时,您是否想要覆盖Fruit.Peel?很可能不希望这样做-它可能具有完全不同的含义。相反,您可以使用Banana.Peel方法隐藏它,并且所有现有的代码仍然按照今天的方式工作。

换句话说,这主要是为了避免版本问题。在Java中,即使您不想这样做,也会覆盖Fruit.peel,这可能导致难以诊断的错误。


好的例子。这是一个非常方便的选项,但我认为它有足够的陷阱需要编译器发出警告。 - PeterR
1
如果你隐藏了一个方法但没有使用 new,那么编译器会发出警告。这个想法是,如果你明确地添加了一个修饰符,那就意味着你真的是有意为之 :) - Jon Skeet

2
从个人经验来看,我主要看到“new”关键字被用于原始父方法未指定为虚拟的情况下,但是希望覆盖行为。应用“new”关键字会“隐藏”父方法。正如您在代码示例中观察到的那样,使用“new”编写的方法仅在直接使用该类型时才会执行。如果使用父类型,则将调用父原始方法。
更直接地回答您的问题 - 它提供了一种覆盖方法的手段,当父方法未标记为虚拟时。
编辑:顺便说一句,添加“new”关键字以隐藏不自然可覆盖的父方法实际上不会改变生成的IL中的任何内容。但这是一种明确告诉开发人员“嘿,您在这里隐藏了一个父方法,而不是覆盖它”的方式。

0
public class BaseCollection<T>
{
  // void return - doesn't seem to care about notifying the 
  // client where the item was added; it has an IndexOf method 
  // the caller can use if wants that information
  public virtual void Add(T item)
  {
    // 添加某项,但不指定添加的位置
  }
public int IndexOf(T item) { // 告知该项的位置 } } public class List<T> : BaseCollection<T> { // 这里我们使用了Int32返回值,因为我们友好的List将告诉调用者该项被添加到了哪个位置 new public virtual int Add(T item) // <-- clearly not an override { base.Add(item); return base.IndexOf(item); } }

这里我使用了“new”修饰符,因为List<T>引用会隐藏BaseCollection<T>中的Add方法。默认情况下,从基类中隐藏成员会生成编译器警告(如果你的编译设置为失败时出现警告,则会生成错误)。所以我基本上是在告诉编译器...“是的,我知道我用void返回值隐藏了Add方法,但这是期望的功能——就这样吧。”


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接