为什么覆盖C#中的静态方法

And*_*asN 8 c# inheritance static

protected static new void WhyIsThisValidCode()
{
}
Run Code Online (Sandbox Code Playgroud)

为什么允许覆盖静态方法?除了bug之外什么也没有,它根本不会像你想象的那样起作用.

参加以下课程.

class BaseLogger
{
    protected static string LogName { get { return null; } }

    public static void Log(string message) { Logger.Log(message, LogName); }
}

class SpecificLogger : BaseLogger
{
    protected static string LogName { get { return "Specific"; } }
}
Run Code Online (Sandbox Code Playgroud)

这是和,代码

SpecificLogger.Log("test");
Run Code Online (Sandbox Code Playgroud)

是的,但它不会通过查看代码来实现您的想法.

它打电话Logger.LogLogName = null.

那为什么允许这样做?

Jar*_*Par 24

new关键字不覆盖的方法.它改为创建一个与原始方法无关的同名新方法.无法覆盖静态方法,因为它们不是虚拟方法

  • 我有时会错过C#中的虚拟类方法.但它们可能通常不足以保证将它们添加到语言中. (2认同)

Cod*_*aos 15

你没有压倒它,你正在隐藏它.普通方法会表现出完全相同的行为,因此这里没有任何特定的静态方法.

隐藏仅在少数情况下有用.我经常遇到的一个问题是在派生类中使返回类型更具体.但我从来没有使用静态方法.

具有特定名称的静态函数可能有用的一个区域是,如果您使用反射并希望通过从方法返回来获取每个类的信息.但是在大多数情况下,属性更适合.

并且它不可能创建错误,因为您的代码产生了编译器警告:

警告'StaticHiding.SpecificLogger.LogName'隐藏继承的成员'StaticHiding.BaseLogger.LogName'.如果要隐藏,请使用new关键字.

如果你使用new你应该知道你在做什么.


Ian*_*ths 11

其他人已经指出这不是最重要的,但这仍然留下你原来的问题:为什么你能做到这一点?(但问题是"为什么你可以隐藏静态方法".)

这是支持包含基类和使用这些基类的组件的组件的独立版本控制的不可避免的特性.

例如,假设组件CompB包含基类,而其他组件CompD包含派生类.在CompB的第1版中,可能没有任何名为LogName的属性.CompD的作者决定添加一个名为LogName的静态属性.

在这一点上要理解的关键是,CompD的v1的作者并不打算替换或隐藏基类的任何特性 - 在编写该代码时,基类中没有名为LogName的成员.

现在想象一下,发布了新版本的CompB库.在这个新版本中,作者添加了一个LogName属性.CompD应该发生什么?选项似乎是:

  1. CompD不再有效,因为它引入的LogName与添加到CompB的LogName冲突
  2. 以某种方式使CompD的LogName替换基础CompB LogName.(实际上并不清楚这对于静力学是如何起作用的.你可以用非静力学来设想这个.)
  3. 将两个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");
Run Code Online (Sandbox Code Playgroud)

但是C#编译器发现SpecificLogger没有提供Log方法,因此将其转换为:

BaseLogger.Log("test");
Run Code Online (Sandbox Code Playgroud)

因为那是定义Log方法的地方.

因此,无论何时在派生类中定义不尝试覆盖现有方法的方法,C#编译器都会在元数据中指明这一点.(方法元数据中有一个"newslot"设置说,这个方法是全新的,与基类中的任何东西无关.)

但是,如果要重新编译CompD,这会给您带来问题.假设您有一些完全不相关的代码,并且您需要发布新版本的CompD,因此您有一个错误报告.您可以根据CompB的新版本对其进行编译.如果您编写的代码不被允许,您实际上将无法 - 已经编译的旧代码可以工作,但您将无法编译该代码的新版本,这将有点疯狂.

因此,为了支持这种(坦率地说有点模糊)的场景,他们允许你这样做.他们会发出警告,告诉您这里有一个命名冲突.你需要提供new关键字来摆脱它.

这是一个模糊的场景,但是如果你想支持跨组件边界的继承,你需要这样做,否则在基类上添加新的公共成员或受保护成员将不可避免地成为一个重大变化.这就是为什么会在这里.但依靠它是不好的做法,因此你得到编译器警告的事实.使用new关键字去除警告应该只是一个权宜之计.

底线是这样的:这个特征仅仅由于一个原因而存在,那就是在一些基类的新版本添加了以前不存在的成员并且与成员发生冲突的情况下让你走出困境那已经在你的派生类上了.如果不是您所处的情况,请不要使用此功能.

(我认为他们实际上应该发出一个错误而不是一个警告,当你省略新的时候,以便更清楚.)