其他人指出这并不是覆盖,但这仍然留下了你最初的问题:为什么你能够这样做?(但问题实际上是“为什么你能隐藏静态方法”)。
这是支持包含基类和使用这些基类的组件的独立版本的不可避免的特性。
例如,想象一下,组件CompB包含基类,而另一个组件CompD包含一个派生类。在CompB的第1个版本中,可能没有名为LogName的属性。CompD的作者决定添加一个名为LogName的静态属性。
在这一点上,关键的是要理解CompD的第1个版本的作者并不打算替换或隐藏基类的任何特性 - 当他们编写那段代码时,基类中没有名为LogName的成员。
现在想象一下,CompB库发布了一个新版本。在这个新版本中,作者添加了一个LogName属性。在CompD中应该发生什么?选项似乎是:
CompD不再工作,因为它引入的LogName与添加到CompB的LogName发生冲突。
以某种方式使CompD的LogName替换基础CompB的LogName。(实际上,这如何与静态成员一起工作并不明显。不过,对于非静态成员,你可以设想这一点。)
将这两个LogName成员视为完全不同的成员,只是恰好具有相同的名称。(实际上,它们并不相同 - 它们被称为BaseLogger.LogName和SpecificLogger.LogName。但由于在C#中我们不总是需要用类限定成员名称,所以看起来它们是相同的名称。)
.NET选择执行第3个选项(无论是静态还是非静态)。如果您想要第2个行为-替换-与非静态一起使用,则基类必须是
virtual
并且派生类必须将该方法标记为
override
,以明确表示它们有意覆盖了基类中的方法。除非派生类明确声明希望这样做,否则C#永远不会使派生类的方法替换基类的方法。这很可能是安全的,因为这两个成员之间没有关联-基类的LogName甚至在引入派生类时都不存在。与仅仅因为基类的最新版本引入了新成员而导致的简单中断相比,这是更可取的。
如果没有这个功能,新版本的.NET Framework将无法向现有的基类添加新成员,而不会导致破坏性的改变。
你说行为不符合你的期望。实际上,这正是我所期望的,也是你在实践中可能想要的。BaseLogger并不知道SpecificLogger引入了自己的LogName属性。(因为你不能重写静态方法,所以没有机制可以知道。)当SpecificLogger的作者编写LogName属性时,请记住他们是针对没有LogName的BaseLogger的v1版本编写的,所以他们并不打算替换基本方法。既然两个类都不想要替换,显然替换是错误的选择。
唯一可能导致你陷入这种情况的场景是这两个类位于不同的组件中。(显然,你可以构造一个场景,让它们位于同一个组件中,但为什么要这样做呢?如果你拥有这两段代码并将它们发布在一个单独的组件中,这样做是疯狂的。)因此,BaseLogger应该有自己的LogName属性,这正是发生的情况。你可能写过这样的代码:
SpecificLogger.Log("test")
但是C#编译器发现SpecificLogger没有提供Log方法,所以将其转换为:
BaseLogger.Log("test");
因为这是Log方法定义的地方。
所以,每当你在派生类中定义一个方法,而不是试图覆盖现有的方法时,C#编译器会在元数据中指示这一点。(方法元数据中有一个"newslot"设置,表示这个方法是全新的,与基类中的任何内容无关。)
但是,如果你想重新编译CompD,这会给你带来问题。假设你收到了一个由于某个完全不相关的代码问题而导致的错误报告,并且你需要发布CompD的新版本。你将其编译为新版本的CompB。如果你编写的代码是不允许的,实际上你将无法编译 - 已经编译的旧代码可以工作,但你将无法编译那些代码的新版本,这有点疯狂。
因此,为了支持这种(实际上有些晦涩)的情况,他们允许你这样做。他们会生成一个警告,让你知道这里有一个命名冲突。你需要使用"new"关键字来消除它。
这是一个晦涩的情景,但如果你想要支持跨组件边界的继承,你就需要这个功能,否则在基类上添加新的公共或受保护成员将不可避免地引发破坏性变更。这就是为什么这个功能存在的原因。但是依赖它是不好的实践,所以你会得到一个编译器警告。使用
new
关键字来消除警告只应该是一个权宜之计。
底线是:这个功能只存在一个原因,那就是在某个基类的新版本添加了一个之前不存在的成员,并且与你的派生类上已有的成员冲突时,帮助你摆脱困境。如果你不处于这种情况下,请不要使用这个功能。
(我认为他们实际上应该在你省略new时发出错误而不是警告,以使这一点更加清晰。)