为什么我们不能在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
成员可能会有效地增加基类的属性的可见性 – 但要记住,基类的属性仍然可以直接访问(例如,你的子类的一个子类可以将this
为Base
,绕过你的属性)。
这里的问题是如何在子类中override
和new
同名的成员(标识符)。 这显然是不可能的。 至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提出了一个问题。
更好的问题是你为什么要改变访问修饰符?