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

protected static new void WhyIsThisValidCode() { } 

为什么允许覆盖静态方法? 除了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"; } } } 

这是和,代码

 SpecificLogger.Log("test"); 

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

它使用LogName = null调用Logger.Log

那么为什么允许这样做呢?

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

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

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

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

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

警告’StaticHiding.SpecificLogger.LogName’隐藏inheritance的成员’StaticHiding.BaseLogger.LogName’。 如果要隐藏,请使用new关键字。

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

其他人指出这不是压倒一切,但这仍然留下你原来的问题:为什么你能做到这一点? (但问题是“为什么你可以隐藏静态方法”。)

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

例如,假设组件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甚至不存在于引入派生的那个点。 这比简单破解更可取,因为最新版本的基类引入了一个新成员。

如果没有此function,.NET Framework的新版本将无法将新成员添加到现有基类,而不会发生重大变化。

你说这种行为不是你所期望的。 实际上,这正是我所期待的,以及你在实践中可能想要的东西。 BaseLogger不知道SpecificLogger引入了自己的LogName属性。 (没有机制,因为你不能覆盖静态方法。)当SpecificLogger的作者写下LogName属性时,请记住他们正在写一个没有LogName的BaseLogger的v1,所以他们不打算它应该替换基本方法。 由于两个class级都不想更换,明显更换是错误的。

您应该在这种情况下遇到的唯一情况是因为这两个类在不同的组件中。 (显然你可以设计一个场景,当它们在同一个组件中时,但为什么你会这样做呢?如果你拥有这两段代码并在一个组件中释放它们,那么这样做会很疯狂。)所以BaseLogger应该得到它自己的LogName属性,这正是发生的事情。 你可能写过:

 SpecificLogger.Log("test"); 

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

 BaseLogger.Log("test"); 

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

因此,无论何时在派生类中定义不尝试覆盖现有方法的方法,C#编译器都会在元数据中指出这一点。 (方法元数据中有一个“newslot”设置,该方法是全新的,与基类中的任何内容无关。)

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

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

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

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

(我认为他们实际上应该发出一个错误而不是一个警告,当你遗漏新的时,为了使它更清楚。)

静态方法和字段不属于类实例,而属于类定义。 静态方法不参与虚拟调度机制,也不是虚方法表的一部分。

它们只是该特定类的方法和字段。

看起来方法和字段可能是“inheritance的”,因为您可以执行SpecificLogger.Log() ,但这只是让您不必一直引用基类的东西。

静态方法和字段实际上只是全局方法和字段,只是OO类型。

您没有覆盖基类中的属性,而是隐藏它。 运行时使用的实际属性取决于您正在使用的接口。 以下示例说明:

 SpecificLogger a = new SpecificLogger(); BaseLogger b = new SpecificLogger(); Console.Write(a.Log); // Specific Console.Write(b.Log); // null 

在您的代码中,Log方法实际上是针对BaseLogger接口 – 因为Log方法是BaseLogger类的一部分。

无法覆盖静态方法和属性,并且当您要隐藏属性时,应使用new关键字来表示您隐藏了某些内容。

令我惊讶的是,在.net Framework 4.5.1,VS 2013上允许并且编译没有任何错误。

 class A { public static void Foo() { } } class B : A { } class Program { static void main(string[] args) { B.Foo(); } }