为什么我们需要 new 关键字以及为什么要隐藏而不是覆盖默认行为?

我看着这个 博客文章有以下问题:

  • 为什么我们需要 new关键字,它只是为了指定一个隐藏的基类方法。我是说,我们为什么需要它?如果我们不使用 override关键字,我们不是隐藏了基类方法吗?
  • 为什么 C # 中的默认设置是隐藏而不是覆盖? 为什么设计者要这样实现它?
19484 次浏览

在 C # 中,默认情况下成员是密封的,这意味着您不能覆盖它们(除非用 virtualabstract关键字标记)和 这是出于性能原因新的修饰语用于显式隐藏继承的成员。

如果派生类中的方法前面带有 new 关键字,则将该方法定义为独立于基类中的方法

然而,如果你没有指定 new 或者重写,结果输出和你指定 new 是一样的,但是你会得到一个编译器警告(因为你可能没有意识到你在基类方法中隐藏了一个方法,或者你确实想要重写它,只是忘记了包含关键字)。

因此,它可以帮助您避免错误,并明确显示您想要做什么,它使代码更具可读性,所以人们可以很容易地理解您的代码。

值得注意的是,new在此上下文中的 只有效应是抑制警告。语义上没有变化。

因此,一个答案是: 我们需要 new向编译器发出信号,告诉它隐藏的是 故意,并消除警告。

接下来的问题是: 如果不能/不能重写一个方法,为什么要引入另一个具有相同名称的方法?因为隐藏本质上是一种名称冲突。当然,在大多数情况下,你会避免这种情况。

我能想到的故意隐藏的唯一好理由是当一个名字被一个接口强加给你时。

如果在没有指定 override关键字的情况下重写是默认的,那么由于名称相等,您可能会意外地重写基础的某些方法。

.Net compiler strategy is to emit warnings if something could go wrong, just to be safe, so in this case if overriding was default, there would have to be a warning for each overriden method - something like 'warning: check if you really want to override'.

我的猜测主要是由于多接口继承。使用谨慎的接口,两个不同的接口很可能使用相同的方法签名。允许使用 new关键字将允许您用一个类创建这些不同的实现,而不必创建两个不同的类。

更新 ... < em > Eric 给了我一个关于如何改进这个示例的想法。

public interface IAction1
{
int DoWork();
}
public interface IAction2
{
string DoWork();
}


public class MyBase : IAction1
{
public int DoWork() { return 0; }
}
public class MyClass : MyBase, IAction2
{
public new string DoWork() { return "Hi"; }
}


class Program
{
static void Main(string[] args)
{
var myClass = new MyClass();
var ret0 = myClass.DoWork(); //Hi
var ret1 = ((IAction1)myClass).DoWork(); //0
var ret2 = ((IAction2)myClass).DoWork(); //Hi
var ret3 = ((MyBase)myClass).DoWork(); //0
var ret4 = ((MyClass)myClass).DoWork(); //Hi
}
}

问得好,我再说一遍。

为什么用另一个方法隐藏一个方法是合法的?

让我用一个例子来回答这个问题,您有一个来自 CLR v1的接口:

interface IEnumerable
{
IEnumerator GetEnumerator();
}

好极了。现在在 CLR v2中有了泛型,你会想: “伙计,如果我们在 v1中有了泛型,我就会把它变成一个泛型接口。但我没有。我现在应该做一些与 通用性相兼容的东西,这样我就能获得通用性的好处,同时又不会失去与期望 IEnumable 的代码的向后兼容性。”

interface IEnumerable<T> : IEnumerable
{
IEnumerator<T> .... uh oh

您将如何调用 IEnumerable<T>的 GetEnumerator 方法?请记住,您可以 想要它以在非泛型基本接口上隐藏 GetEnumerator。除非显式地处于向后紧凑的情况,否则 永远不会希望调用这个东西。

这就证明了方法隐藏的合理性。更多关于方法隐藏合理性的思考见 我关于这个主题的文章

为什么没有“新”字就躲起来会引起警告?

因为我们想让你们注意到你们在隐瞒什么而且可能是不小心做的。请记住,您可能意外地隐藏了一些内容,因为对基类的编辑是由其他人完成的,而不是通过编辑派生类完成的。

为什么没有“ new”的隐藏是一个警告而不是一个错误?

同样的原因。您可能意外地隐藏了一些内容,因为您刚刚选择了一个新版本的基类。这种事经常发生。FooCorp 创建了一个基类 B.BarCorp 创建了一个带有方法 Bar 的派生类 D,因为他们的客户喜欢这个方法。FooCorp 看到了这一点,然后说,嘿,这是个好主意,我们可以把这个功能放在基类上。他们这样做了,并发布了 Foo.DLL 的新版本,当 BarCorp 拿起新版本时,如果他们被告知他们的方法现在隐藏了基类方法,那就太好了。

我们希望这种情况是一个 警告而不是一个 错误,因为使它成为一个错误意味着 这是脆性基类问题的另一种形式。C # 经过精心设计,当有人更改基类时,对使用派生类的代码的影响被最小化。

为什么隐藏而不覆盖默认值?

因为虚拟覆盖是 很危险。虚拟重写允许派生类更改编译为使用基类的代码的行为。做一些危险的事情,比如做一个覆盖应该是你做的 有意识地故意的,而不是偶然。

如前所述,方法/属性隐藏使得可以更改方法或属性的内容,而这些内容在其他情况下无法更改。其中一种有用的情况是允许继承的类具有基类中只读的读写属性。例如,假设一个基类具有一组名为 Value1-Value40的只读属性(当然,实类会使用更好的名称)。此类的密封后代具有一个构造函数,该构造函数接受基类的一个对象并从该对象复制值; 该类不允许在此之后更改这些值。一个不同的、可继承的后代声明了一个名为 Value1-Value40的读写属性,该属性在读取时的行为与基类版本相同,但在写入时允许写入值。最终的结果是,代码想要一个基类的实例,它知道这个基类永远不会改变,可以创建一个只读类的新对象,它可以从传入的对象复制数据,而不必担心该对象是只读还是读写。

这种方法的一个麻烦之处在于——也许有人可以帮助我——我不知道在同一个类中同时隐藏和覆盖特定属性的方法。有哪种 CLR 语言允许这样做(我使用 vb2005) ?如果基类对象及其属性可以是抽象的,那么这将非常有用,但是这需要一个中间类在子类可以隐藏它们之前将 Value1重写为 Value40属性。