为啥我们不能在 C# 中覆盖方法时更改访问修饰符?
Posted
技术标签:
【中文标题】为啥我们不能在 C# 中覆盖方法时更改访问修饰符?【英文标题】:Why can't we change access modifier while overriding methods in C#?为什么我们不能在 C# 中覆盖方法时更改访问修饰符? 【发布时间】:2011-09-08 09:04:04 【问题描述】:在 C# 中,我们不能在覆盖基类的方法时更改访问修饰符。 例如
Class Base
**protected** string foo()
return "Base";
Class Derived : Base
**public** override string foo()
return "Derived";
这在 C# 中无效,它会给出编译时错误。
我想知道原因,为什么不允许。是否存在任何技术问题或是否会导致访问限制不一致???
【问题讨论】:
【参考方案1】:覆盖是一个术语,它使您能够更改或增强基类中方法的行为。覆盖使您可以控制为现有方法编写新逻辑。
更改基类的方法签名有点像编写新方法而不是覆盖现有方法。它与覆盖方法的目的相矛盾。所以也许是在 C# 中覆盖方法时无法更改访问修饰符的原因。
【讨论】:
【参考方案2】:降低可见性是不可能的,因为如果Base.Member
可见而Derived.Member
不可见,这将破坏OOP 中“Derived
是Base
”的整个概念。但是,也许不允许增加可见性,因为语言开发人员认为在大多数情况下更改可见性是一个错误。但是,您始终可以使用new
关键字通过引入具有相同名称但行为不同的成员来隐藏基类成员。这个新成员属于派生类型的接口,因此您当然仍然可以通过强制转换为该基类型来访问基类型的接口。根据您编写子类的方式,您的 new
成员可能会有效地增加基类属性的可见性——但请记住,仍然可以直接访问基类的属性(例如,您的子类的子类可以将 this
转换为Base
并绕过您的财产)。
这里的问题是如何同时 override
和 new
子类中的同名成员(标识符)。这显然是不可能的。至少,我可以通过实验说public new override string foo()return "";
不是这样的语法。但是,您可以通过使用两个子类来获得相同的效果:
using System;
class Base
protected virtual string foo()
return "Base";
public void ExhibitSubclassDependentBehavior()
Console.WriteLine("Hi, I am 0 and 1.", GetType(), foo());
abstract class AbstractDerived : Base
protected virtual string AbstractFoo()
return base.foo();
protected override string foo()
return AbstractFoo();
class Derived : AbstractDerived
protected override string AbstractFoo()
return "Deprived";
public new string foo()
return AbstractFoo();
static class Program
public static void Main(string[] args)
var b = new Base();
var d = new Derived();
Base derivedAsBase = d;
Console.Write(nameof(b) + " -> "); b.ExhibitSubclassDependentBehavior(); // "b -> Hi, I am Base and Base."
Console.WriteLine(nameof(d) + " -> " + d.foo()); // "d -> Deprived"
Console.Write(nameof(derivedAsBase) + " -> "); derivedAsBase.ExhibitSubclassDependentBehavior(); // "derivedAsBase -> Hi, I am Derived and Deprived."
中间子类 (AbstractDerived
) 使用 override
并引入了一个新的、不同名称的成员,子类和子子类可以继续使用它们认为合适的基类成员 override
。子类 (Derived
) 使用 new
引入新的 API。由于您只能在每个子类化级别一次将new
或override
与特定标识符一起使用,因此您需要两个级别的子类化才能在同一标识符上有效地使用两者。
因此,在某种程度上,您可以在覆盖方法的同时更改可见性——这只是一种痛苦,而且我不知道只用一个继承级别来完成它的语法。但是,您可能必须使用类似这样的技巧,具体取决于您尝试实现的接口以及基类的外观。即,这可能是也可能不是您真正想要做的。但我仍然想知道为什么 C# 不只是支持这一点。 IOW,这个“答案”只是用解决方法重新表达了 OP 的问题 ;-)。
【讨论】:
【参考方案3】:在派生类型中更改方法的访问修饰符是没有意义的,这就是不允许这样做的原因:
案例 1:使用更严格的访问权限覆盖
由于以下情况,这种情况显然是不允许的:
class Base
public virtual void A()
class Derived: Base
protected override void A()
现在我们可以说:
List<Base> list;
list.Add(new Derived());
list[0].A() //Runtime access exception
案例 2:使用限制较少的访问修饰符覆盖
有什么意义?隐藏方法,你就完成了。
显然,如果有人通过基类型调用,他们将无法访问派生类型中定义的新方法,但这与基类型的作者想要的东西是一致的,所以你没有“权利”来改变它。如果你想从派生类调用派生类的细节,在这种情况下new
方法工作得很好。
编辑:扩展案例 2
在案例 2 中我想说的是,如果您想更改可访问性,您已经有办法更改任何方法(虚拟或非虚拟)的可访问性。
考虑以下代码:
public class Base
protected virtual string WhoAmI()
return "Base";
public class Derived : Base
public new virtual string WhoAmI()
return "Derived";
public class AnotherDerived : Derived
public override string WhoAmI()
return "AnotherDerived";
使用new
关键字,您有效地为Derived
类创建了一个具有相同名称和签名的新虚拟方法。请注意,允许声明 new
方法 virtual
,因此任何派生自 Derived
的类都将被允许覆盖它。
不允许有人做以下事情:
Base newBaseObject = new Derived();
newBaseObject.WhoAmI() //WhoAmI is not accessible.
但这个事实与能否覆盖WhoAmI()
无关。无论如何,这种情况永远不会发生,因为Base
没有声明public
WhoAmI()
。
因此,在理论上的 C# 中,Derived.WhoAmI()
可以覆盖 Base.WhoAmI()
,这样做没有实际好处,因为无论如何您将永远无法从基类调用虚方法,因此 new
选项已经满足您的要求。
我希望这会让它更清楚。
【讨论】:
您好,InBetween,感谢您的帮助。我非常相信你在这里解释的案例 1。但是我仍然对案例 2 感到困惑。当我覆盖一个方法时,无论如何我都会覆盖(隐藏)基类方法的行为。所以没有什么可以改变基类的东西。 我不明白你怎么说“这样做没有实际好处,因为无论如何你永远无法从基类调用虚拟方法”。基类定义protected virtual
方法以使子类能够更改基类中的实现所表现出的行为是很常见的。关于案例 2 的问题是如何 1. 覆盖 protected vritual MemberName
和 2. 公开一个名为 MemberName
的公共成员。听起来如果不将单个子类替换为两个子类以增量override
然后new
是不可能的。
@binki 我根本不关注你。我在哪里说过受保护的成员并覆盖它们没有用? OP 询问为什么在重写此类方法时不允许更改访问修饰符,而我的回答正是解决了这个问题:重写受保护的方法 并更改其访问修饰符 对您有什么帮助?它比 new 好在哪里?
@InBetween,引号之间的文字逐字逐句摘自您的回答。有时new
是不够的。如果您需要您的子类覆盖基类中的某些行为并希望使用 same identifier 作为子类公共接口的一部分,那么override
是必要的(并且使用override
会阻止您使用new
)。请参阅my answer,我在其中演示了如何同时将override
和new
成为成员......
@binki 我的问题仍然存在:覆盖受保护的方法并将其公开有什么好处?基类的公共API就是这样,你不能改变,你没有权利;在派生类中公开所述方法不会改变基类中方法的可访问性,因此您不能从一开始就利用虚拟调用。这就是 new 本质上已经达到目的的原因; BaseClass.ProtectedChangedToPublicOverridenMethod
不是有效调用,因为 ProtectedChangedToPublicOverridenMethod
不是 BaseClass
的公共 API 的一部分【参考方案4】:
好的,我在 Annotated C# 参考中找到了 Eric Lippert 的一个小注释:
被覆盖的虚方法仍然被认为是引入它的类的方法。在某些情况下,重载决策规则更喜欢派生类型的成员......覆盖方法不会“移动”该方法在此层次结构中所属的位置。
所以这是一个有意的规则,以防止“脆弱的基类”问题并提供更好的版本控制,即当基类更改时问题更少。
但请注意,它与安全性、类型安全性或对象状态无关。
【讨论】:
我认为它只是没有意义。请参阅我的推理答案。 我无法理解你句子的第一部分。 对不起手机自动完成...我的意思是我发现在覆盖时更改访问权限的能力毫无意义。 因此,您是说当基类成员的可见性发生更改时,您在子类中遇到的编译器错误通过迫使开发人员在那时审查子类来避免脆弱的基类问题。这听起来确实很有用。【参考方案5】:如果它有不同的访问修饰符,你就不能再认为它是同一种方法了。有点暗示模型设计存在问题。
更好的问题是为什么要更改访问修饰符?
【讨论】:
【参考方案6】:如果您将可见性修饰符从限制性更强的修饰符更改为限制性较小的修饰符,则您允许类客户端访问指定为内部使用的方法。本质上,您提供了一种更改可能不安全的类状态的方法。
【讨论】:
class Derived public string foo2() return foo();
所以我认为你的论点还不够。
@Henk :您可以规避类可见性修饰符的面孔并没有改变您允许访问指定为内部使用的可能导致问题的方法的事实。在这种情况下,可能不会出现任何问题,或者事情可能会爆炸。如果您发现包装非公共成员以使其可公开访问不会导致问题,则类设计可能是错误的。这并不意味着规避可见性修饰符是一个好主意。
我期待在接口和替代方面的答案。派生类总是可以添加到公共接口,它不能减少它。而关于对象状态的论点根本站不住脚。
@Henk :派生类当然可以添加到接口中,但这不是问题所在。问题是为什么不允许通过覆盖将现有方法的可见性更改为更宽松的级别。该问题与包装或使用非公共方法无关。如问题所示,覆盖不会阻止类替换,也不会添加额外的公共方法(除非客户端检查方法元数据)。
我认为这是问题所在。但请参阅我关于“脆弱基类”的回答。【参考方案7】:
您可以使派生类的访问权限少于基类的访问权限,但不能更多。否则它将与 base 的定义相矛盾,并超出预期的范围暴露其组件。
【讨论】:
不,覆盖时您根本无法更改访问权限。它会如何以任何方式与基类相矛盾?派生类总是有自己的接口。 如果您可以限制访问,您将修改界面。【参考方案8】:原因很明显。对象的安全性和完整性。
在这个特定的示例中,如果外部实体开始修改根据基类保护的对象的属性会怎样。事情会变得一团糟。那么针对所有/任何派生类必须符合的基类编写的客户端代码呢?
【讨论】:
它与安全性无关。并且期望基类的代码根本看不到公共派生方法。 更改被覆盖方法的访问权限是毫无意义的,因为它不会为语言增加任何好处(增加访问权限)或不允许这样做,因为您会破坏基类合同(限制访问权限)。它与安全无关。请参阅我的答案以进行推理。以上是关于为啥我们不能在 C# 中覆盖方法时更改访问修饰符?的主要内容,如果未能解决你的问题,请参考以下文章