为什么这个C#类声明会编译通过?

8
这个问题有点毫无意义,但我只是好奇:
这个:
public sealed class MyClass
{
   protected void MyMethod(){}
}

编译通过,但是会有警告

而这段代码:

public sealed class MyClass
{
   public virtual void MyMethod(){}
}

无法编译。只是出于好奇,这有什么原因吗?


9
我愿意回答,但不知为何我认为JS会有更好的答案。嗯。 - Sam Harwell
2
显然,JS正在将SO优先于THREAD_PRIORITY_BELOW_NORMAL-快点,趁你还能抢答! - cfeduke
+1 好问题,我甚至从未遇到过这种情况,因为我从未考虑过这样做...因为这毫无意义! - Stan R.
9个回答

12

virtual用于声明一个方法或属性可被“重载”。

sealed用于声明该类不能被继承。

因此,在一个 sealed 类中,虚方法永远不可能被重载,因为该类无法被继承。这就没有意义了。

protected影响成员的访问权限,但不像 virtual 一样声明其可“重载”(虽然通常以这种方式使用),因此并不矛盾。


是的,我理解了。请注意,我没有问“为什么这个编译不通过”,而是问为什么这个可以编译通过。同样的道理,在一个密封类中使用虚方法是没有意义的,受保护的方法也是一样,你明白我的意思吗? - BFree
我认为原帖作者意识到sealedvirtualprotected不太相符合。他想知道为什么一个“不合理”的项目被允许,而另一个则不行。 - Sam Harwell
不仅毫无意义,而且也是不可能的。Sealed 显式地防止了虚函数调用的开销(MSIL 的 'call' 与 'callvirt')。 - cfeduke
@Kevin:CLI 对虚方法有很多限制。例如,它们不能是静态的,也不能是构造函数。此外,抽象和密封方法 必须 是虚方法。 - Sam Harwell
protected还控制着类外部的访问。当然,通过将构造函数设为私有(这也是为什么会出现警告),你也可以获得相同的行为,但主要的区别在于它实际上有所不同。虚拟方法除了直接与sealed相矛盾之外,没有其他影响。 - Tal Pressman
显示剩余6条评论

4
我能想到的唯一原因是,有时需要编写受保护的方法来覆盖其他受保护的方法。语言设计本可以允许这样做:
protected override void Foo()

但不包括这个

protected void Foo()

但这可能会被认为有点难以理解 - 就是override缺失使其无用,而在另一种情况下

public virtual void Foo()

虚拟的存在是没有用的,而“错误”的存在可能比有用的东西的缺失更容易理解。

在这种情况下,虚拟可能会对性能产生影响,而将某些内容保护起来而不是私有的可能不会产生影响 - 所以它更严重一些。

这只是猜测 - 如果我们真的很幸运,Eric Lippert会给出一个更明确的答案。他才是你想要的人,而不是我 :)

最佳答案:将警告视为错误,它们就是等价的;)


哦,好主意,我没有考虑到封闭类正在从基类继承并覆盖受保护成员的情况。这可能很有道理。昨晚我实际上给 Eric 发了电子邮件,他回复说他会调查一下并回复我。在他回复之前,我将保持此问题开放。谢谢 Jon。 - BFree
2
嘿,伙计们,我也只是猜测而已。语言设计笔记文件表明,在1999年10月18日做出了将在密封类型中引入虚拟方法视为错误的决定,但并没有给出任何决策的理由。我找不到任何笔记中证明为什么引入新的受保护成员应该是合法的。我最好的猜测是:这可能只是第一个版本中的疏忽,然后修复它变成了一个破坏性的改变。 - Eric Lippert
2
@Eric:出于兴趣,这在规范的哪里?它似乎不在10.6的介绍部分中列出的有效修饰符组合规则中。我以为我会找到规则,然后看看它是否在任何注释规范中提到...现在我找不到规则了。我在10.6.3中也找不到它。 - Jon Skeet
嗯...我也找不到它。显然这应该写入规范中,规则就在注释里。我会将其添加到我们漫长的规范遗漏清单中。感谢您让我注意到这个问题。 - Eric Lippert
@Eric,这可能是实现挑战影响设计决策的问题吗?似乎有点奇怪,因为密封类的基类可以有虚方法,但密封类本身不能有。话虽如此,它需要确保所有虚方法都被填充...*(但是,我承认在一个密封类中阅读虚拟或受保护的内容看起来有点傻)*。 - Jason D

4

我看不出来这样做有什么好处。受保护的MyMethod可以从MyClass中调用,但永远不会从派生类中调用(因为MyClass是密封的)。虚拟版本也允许直接从MyClass中调用,但方法不能有重写,因为你不能从MyClass派生一个类...


2

封闭类可以通过继承拥有受保护的成员。

当一个方法是一个类的一部分时,无论这个方法是如何得到的都没有关系。

在第一种情况下,对于封闭类上的受保护方法,它与封闭类继承受保护方法是相同的。因此,它可以编译。

出于好奇,到底给出了什么警告?


警告信息为: “warning CS0628: 'SimpleTestApp.Class1.foo()': 在密封类中声明了新的受保护成员。” - Jason D

2
错误信息如下:

CS0549:'function'是封闭类'class'中的一个新虚成员。

首先,虽然在封闭类中包含新的protectedvirtual成员并不合理,但CLI¹确实允许这样做。CLI还允许使用callvirt IL指令调用封闭类的成员,尽管编译器可以自由地将其替换为call指令。
目前,在ECMA-334(C#语言规范)中我找不到任何要求编译器发出上述错误的内容。似乎是因为Microsoft的实现添加了该错误,只是因为在封闭类中包含新的虚成员不太合理。
¹CLI是一种虚拟机,C#编译器发出字节码并在其上运行。几乎在CLI中违法的任何概念对于C#也是非法的 - 但这是一种情况,其中C#做了一些额外的事情(并不是一个问题)。
编辑:似乎被标记为解释为什么在OP中不应该编写这样代码的帖子是错的。但关于哪个规则使其成为编译器错误,他们看起来是错误的。

1
一个密封类不能被子类化,因此虚方法不是一个选项。因此出现错误。 这第一个有点愚蠢但有效,因此发出警告。

0

作为密封的当应用于类时,sealed修饰符会阻止其他类从它继承。

在这里我正在逐一解释:

public sealed class MyClass
{
   protected void MyMethod(){}
}

它会给你警告,因为实际上这是没有意义的。因为在将类声明为sealed之后,您无法继承它,而且由于您的方法是protected,所以您无法使用其对象在类外部访问它(还要记住,您也无法创建此类的子类,因此您也无法通过该技巧使用此方法)。因此,在实践中,将其设置为protected是没有意义的,因此编译器会给出警告。但是,如果将其设置为publicinternal,则不会出现错误,因为在这种情况下它是有用的。

现在是第二个问题:

public sealed class MyClass
{
   public virtual void MyMethod(){}
}

你已经将类封装起来,现在你正在将方法设置为虚拟的,这样间接地给了别人覆盖它的机会,而这只能通过继承实现。这里出现了问题,因为你的类是被封装的,所以无法对该类执行继承操作。这就是为什么使用 virtual 会出错。

希望这可以帮助你理解。

参考链接 http://msdn.microsoft.com/en-us/library/88c54tsw.aspx


0
我猜编译器对于密封类进行了一些优化,如果你声明了一个虚方法,这些优化就不可能实现 - “没有vtable”似乎是一个合理的候选项。
不过这只是我的猜测。

0
声明一个新的受保护成员意味着有意与派生类共享该成员。密封类不能有派生类,因此声明一个新的受保护成员有点自相矛盾,就像在密封类中声明一个新的虚拟方法一样。
至于为什么虚拟方法会产生错误,而受保护只会产生警告,我只能推测可能与新的虚拟方法需要编译器构建类型的数据结构(vtable)有关,而新的受保护成员只设置了访问标志 - 没有新的数据结构。如果编译器被禁止为密封类创建vtable,那么如果遇到新的虚拟方法,它应该怎么做?编译失败。在密封类中添加新的受保护方法是没有意义的,但不需要编译器进入禁止区域。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接