为什么我们需要新的关键字,为什么是默认的行为隐藏和不重写?

我正在看这个博客文章 ,有以下问题:

  • 为什么我们需要new关键字,只是指定一个基类方法被隐藏。 我的意思是,我们为什么需要它? 如果我们不使用override关键字,我们是不是隐藏基类方法?
  • 为什么在C#中的默认隐藏和不重写? 为什么devise师这样实施呢?

好的问题。 让我重新陈述他们。

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

我举一个例子来回答这个问题。 你有一个来自CLR v1的接口:

 interface IEnumerable { IEnumerator GetEnumerator(); } 

超。 现在在CLR v2中你有generics,你会想:“如果我们只有generics,我会把它作为一个generics接口,但是我没有,现在我应该做一些兼容的东西,我可以从generics的好处中获益,而不会失去与IEnumerable期望的代码的向后兼容性。“

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

你打算怎样调用IEnumerable<T>的GetEnumerator方法? 请记住,您希望它在非通用基础接口上隐藏GetEnumerator。 除非明确地向后兼容,否则你永远不希望这个东西被调用。

这就certificate了方法的隐藏。 欲了解更多关于方法隐藏的理由,请参阅我关于这个主题的文章 。

为什么隐藏没有“新”会引起警告?

因为我们想要引起你的注意,你隐藏了一些东西,可能会意外地做。 请记住,由于编辑了其他人完成的基类,您可能会意外地隐藏某些东西,而不是编辑派生类。

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

同样的道理。 你可能会不小心隐藏了一些东西,因为你刚刚拿起了一个新版本的基类。 这一直发生。 FooCorp创build一个基类B. BarCorp使用方法Bar创build一个派生类D,因为他们的客户喜欢那个方法。 FooCorp看到并说,嘿,这是一个好主意,我们可以把这个function放在基类上。 他们这样做并且发布了一个新版本的Foo.DLL,当BarCorp拿起新版本时,如果他们被告知他们的方法现在隐藏了基类方法,那将是很好的。

我们希望这种情况是一个警告,而不是一个错误,因为使这个错误意味着这是脆弱的基类问题的另一种forms 。 C#经过精心devise,以便有人对基类进行更改时,对使用派生类的代码的影响最小化。

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

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

如果派生类中的方法以new关键字开头,则该方法被定义为独立于基类中的方法

但是,如果您不指定新的或覆盖,则得到的输出与指定新的输出相同,但会得到一个编译器警告(因为您可能不知道在基类方法中隐藏了一个方法,或者实际上你可能想重写它,只是忘了包含关键字)。

因此,它可以帮助您避免错误,并明确地显示您想要执行的操作,并使代码更具可读性,从而轻松理解您的代码。

值得注意的是,在这方面new唯一影响是压制警告。 语义没有变化。

所以一个答案是:我们需要new的信号告诉编译器,隐藏是有意的,并且摆脱警告。

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

我能想到的有意隐藏的唯一好理由是当一个界面强加一个名字时。

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

如果重写是默认的,但没有指定override关键字,则可能会由于名称相等而意外地重写基础的某些方法。

.Net编译器的策略是在出现错误时发出警告,为了安全起见,所以在这种情况下,如果重写是默认的,那么每个重写方法都必须有一个警告 – 就像'警告:检查你是否真的想要覆盖“。

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

更新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 } } 

如上所述,方法/财产隐藏使得可以改变关于方法或财产的事情,否则不能轻易改变。 在这种情况下,可以使用的一种情况是允许inheritance的类具有在基类中只读的读写属性。 例如,假设一个基类有一堆称为Value1-Value40的只读属性(当然,真正的类会使用更好的名字)。 这个类的密封后裔有一个构造函数,它接受基类的对象并从那里复制值; 之后,class级不允许改变。 一个不同的,可inheritance的后代声明了一个名为Value1-Value40的读写属性,读取时,其行为与基类版本相同,但写入时允许写入值。 最终效果是代码,它需要一个它所知道的基类实例永远不会改变的代码可以创build一个只读类的新对象,它可以从传入的对象中复制数据,而不必担心该对象是只读的还是读写的。

这种方法的一个烦恼 – 也许有人可以帮助我 – 是我不知道在同一个class级中阴影和重写一个特定属性的方法。 是否有任何CLR语言允许(我使用vb 2005)? 如果基类对象及其属性可能是抽象的,但是这将需要一个中间类重写Value1到Value40属性,然后子类可以对它们进行投影。

你是正确的,新的关键字显式隐藏基类的成员,以便调用者最终使用您的子类,而不是超类的成员。 那篇博文也提到,你也可以隐含地这样做。

这与重写基类成员不同。 要做到这一点基类成员必须被标记为抽象或虚拟。 所以不是基类中的每个成员都可以被覆盖。 假设你从其他人的程序集中inheritance了一个类,并且你没有权限访问或者不愿意去修改那些代码来使成员变成虚拟的。 没有新的关键字,你会被卡住。

你可以争论是否有更好的关键字,并明确(我的意见)或只是隐式做。