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

本文关键字:访问 方法 不能 我们 覆盖 为什么 | 更新日期: 2023-09-27 17:58:20

在 C# 中,我们不能在覆盖基类的方法时更改访问修饰符。例如

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

这在 C# 中无效,它会给出编译时错误。

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

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

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

案例 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类创建了一个具有相同名称和签名的新虚拟方法。请注意,ALLOW 可以声明一个 new 方法virtual,因此任何从 Derived 派生的类都将被允许覆盖它。

不允许的是让某人执行以下操作:

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

但这一事实与是否能够覆盖WhoAmI()无关。 无论如何,这种情况永远不会发生,因为Base没有声明public WhoAmI() .

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

我希望这能更清楚地说明这一点。

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

重写的虚拟方法仍被视为引入它的类的方法。 在某些情况下,重载解析规则更喜欢更多派生类型的成员...重写方法不会"移动"该方法在此层次结构中所属的位置。

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

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

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

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

这里的问题是如何在子类中overridenew相同的命名成员(标识符(。这显然是不可能的。至少,我可以通过实验说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。由于每个子类化级别只能将具有特定标识符的newoverride使用一次,因此需要两个级别的子类化才能在同一标识符上有效地使用这两个级别。

因此,在某种程度上,您可以在覆盖方法的同时更改可见性 - 这只是一种痛苦,据我所知,没有语法可以仅通过一个继承级别来完成它。但是,您可能必须使用一些这样的技巧,具体取决于您尝试实现的接口以及基类的外观。也就是说,这可能是也可能不是你真正想做的。但我仍然想知道为什么 C# 一开始就不支持这一点。IOW,这个"答案"只是对 OP 问题的重新表达,带有解决方法;-(。

可以使派生类的访问小于基类的访问,但不能更多。 否则,它将与base的定义相矛盾,并使其组件超出预期范围。

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

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

如果它有不同的访问修饰符,你就不能再认为它是相同的方法了。 有点表明模型的设计存在问题。

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

重写是一个术语,可用于更改或增强基类中方法的行为。 重写为您提供了为现有方法编写新逻辑的控制。

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