为什么我们不能在C#中重写方法时更改访问修饰符?

在C#中,我们不能改变访问修饰符,而从基类重写一个方法。 例如

Class Base { **protected** string foo() { return "Base"; } } Class Derived : Base { **public** override string foo() { return "Derived"; } } 

这在C#中是无效的,它会给编译时间带来错误。

我想知道原因,为什么不允许。 有没有技术上的问题,或者是否会导致在访问限制方面不一致?

在派生types中更改方法的访问修饰符是毫无意义的,这就是为什么不允许:

情况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:用较less限制的访问修饰符重写

有什么意义? 隐藏方法,你就完成了。 显然,如果有人通过基types调用,他们将无法访问在派生types中定义的新方法,但是与基types的作者想要的是一致的,所以你没有“正确的”来改变它。 如果你想从派生类派生类调用的细节,在这种情况下, 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类创build一个具有相同名称和签名的新虚拟方法。 请注意,它是允许声明一个new方法virtual ,所以Derived任何类将被允许覆盖它。

什么是不允许的是有人做以下事情:

  Base newBaseObject = new Derived(); newBaseObject.WhoAmI() //WhoAmI is not accessible. 

但是这个事实与能否覆盖WhoAmI()无关。 无论如何,这种情况永远不会,因为Base不宣布public WhoAmI()

因此,在一个理论上的C#中, Derived.WhoAmI()可以重写Base.WhoAmI() ,这样做没有实际的好处,因为无论如何你永远无法从基类调用虚方法,所以new选项已经满足要求。

我希望这更清楚。

好的,我在注释的C#参考中find了Eric Lippert的一个小logging:

被重写的虚拟方法仍然被认为是引入它的类的一种方法。 在某些情况下,重载parsing规则更喜欢更多派生types的成员…重写方法不会“移动”该方法在此层次结构中所属的位置。

所以这是一个有意的规则来防止“脆弱的基类”问题,并提供更好的版本控制,即当基类发生变化时问题less。

但请注意,它与安全性,types安全性或对象状态无关。

如果将可见性修饰符从更具限制性的修改器更改为更less限制性的修饰符,则允许类客户端访问指定用于内部使用的方法。 本质上你已经提供了一种方法来改变可能不安全的类的状态。

你可以让派生类的访问less于基数,但不能更多。 否则,它会违背基地的定义,并暴露其组件超出预期。

减less可视性是不可能的,因为如果Base.Member可见并且Derived.Member不可见,那么这将破坏OOP中的整个“ Derived is a Base ”概念。 然而,越来越多的知名度是不允许的, 也许是因为语言开发者认为改变可见性大多数时候是错误的。 但是,通过引入具有相同名称但具有不同行为的成员,您始终可以使用new关键字来隐藏基类成员。 这个新成员属于派生types的接口,所以当然你仍然可以通过转换为基types来访问基types的接口。 根据你如何编写你的子类,你的new成员可能会有效地增加基类的属性的可见性 – 但要记住,基类的属性仍然可以直接访问(例如,你的子类的一个子类可以将thisBase ,绕过你的属性)。

这里的问题是如何在子类中overridenew同名的成员(标识符)。 这显然是不可能的。 至less,我可以通过实验说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每个级别的子类,所以需要两个级别的子类来有效地使用相同的标识符。

所以,在某种程度上,您可以在重写方法的同时更改可见性 – 这只是一个痛苦,而且我知道只有一个级别的inheritance就可以完成它。 然而,你可能不得不使用一些这样的技巧,这取决于你想要实现的接口以及你的基类是什么样的。 也就是说,这可能是也可能不是你真正想做的事情。 但是我仍然想知道为什么C#不只是支持这个开始。 IOW,这个“答案”只是用解决方法重新expression了OP的问题;-)。

原因是显而易见的。 安全和完整的对象。

在这个特定的例子中,如果外部实体开始修改受基类保护的对象的属性会怎样。 事情会变得不合时宜。 关于所有派生类必须符合的基类的客户代码呢?

如果它有不同的访问修饰符,你不能再真正考虑它是相同的方法。 这种模式的devise提出了一个问题。

更好的问题是你为什么要改变访问修饰符?