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

时间:2011-06-04 13:08:59

标签: c# oop access-modifiers

在C#中,我们无法在从基类覆盖方法时更改访问修饰符。 e.g。

Class Base
{
   **protected** string foo()
   {
       return "Base";
   }
}

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

这在C#中无效,会产生编译时错误。

我想知道原因,为什么不允许这样做。是否存在任何技术问题,或者是否会导致访问限制方面不一致的内容?

8 个答案:

答案 0 :(得分:27)

更改派生类型中方法的访问修饰符毫无意义,这就是为什么不允许这样做的原因:

案例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(),这样做没有任何实际好处,因为无论如何你永远无法从基类调用虚方法,所以{{{ 1}}选项已满足您的要求。

我希望这更清楚。

答案 1 :(得分:9)

好的,我在注释的C#引用中找到了Eric Lippert的一个小注:

  

重写的虚方法仍然被认为是引入它的类的方法。在某些情况下,重载决策规则更喜欢更多派生类型的成员...覆盖方法不会“移动”该方法属于此层次结构的位置。

因此,这是一个有意的规则,可以防止“脆弱的基类”问题,并提供更好的版本控制,即基类更改时的问题更少。

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

答案 2 :(得分:3)

如果将可见性修饰符从限制性更强的修饰符更改为限制性较小的修饰符,则允许类客户端访问指定供内部使用的方法。基本上,你提供了一种改变可能不安全的阶级状态的方法。

答案 3 :(得分:1)

您可以使派生类的访问权限少于基数,但不能更多。否则,它会违背基数的定义,并使其组成部分超出预期。

答案 4 :(得分:1)

降低可见度是不可能的,因为如果Base.Member可见并且Derived.Member不可见,那么会破坏整个“Derived在OOP中的Base”概念。但是,不允许增加可见性可能因为语言开发人员认为在大多数情况下改变可见性是一个错误。但是,您始终可以使用new关键字通过引入具有相同名称但行为不同的成员来隐藏基类成员。这个新成员属于派生类型的接口,因此当然您仍然可以通过强制转换为该基类型来访问基类型的接口。根据您编写子类的方式,new成员可能会有效地提高基类属性的可见性 - 但请记住,仍然可以直接访问基类的属性(例如,子类的子类可以强制转换{{ 1}}到this并绕过你的财产。)

这里的问题是如何在子类中 Baseoverride同一个命名成员(标识符)。这显然是不可能的。至少,我可以通过实验说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来引入新API。由于每个子类级别只能使用newnew只有一次的特定标识符,因此需要两级子类化才能在同一个标​​识符上有效地使用它们。 / p>

所以,从某种程度上说,你可以在覆盖方法的同时改变可见性 - 这只是一种痛苦,而且我只知道只用一级继承就可以完成它。但是,您可能必须使用这样的一些技巧,具体取决于您尝试实现的接口以及基类的外观。即,这可能是也可能不是你真正想做的事情。但我仍然想知道为什么C#不仅仅支持这一点。 IOW,这个“答案”只是一个解决方法的OP问题的重新表达; - )。

答案 5 :(得分:0)

原因很明显。对象的安全性和完整性。

在这个特定的例子中,如果外部实体开始修改受基类保护的对象的属性,该怎么办?事情会变得混乱。那么针对所有/任何派生类必须符合的基类编写的客户端代码呢。

答案 6 :(得分:0)

如果它有不同的访问修饰符,你就不能再把它当作同样的方法了。有点暗示了模型设计的问题。

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

答案 7 :(得分:0)

覆盖是一个术语,它使您能够更改或扩充基类中方法的行为。 Overriding使您可以控制为现有方法编写新逻辑。

更改基类的方法签名有点像编写新方法而不是覆盖现有方法。它与重写方法的目的相矛盾。因此,可能是在覆盖C#中的方法时无法更改访问修饰符的原因。