为什么可以';t子类使用基类保护的构造函数创建新对象

本文关键字:创建 构造函数 保护 新对象 对象 基类 子类 为什么 | 更新日期: 2023-09-27 17:58:14

我正在将一些Java代码移植到C#,我遇到了这个用于复制对象的习惯用法:

class Base
{
    int x;
    public Base(int x) { this.x = x; }
    protected Base(Base other) { x = other.x; }
}
class Derived : Base
{
    Base foo;
    public Derived(Derived other)
        : base(other)
    {
        foo = new Base(other.foo); // Error CS1540
    }
}

错误CS1540为:

无法通过"Base"类型的限定符访问受保护成员"Base.Base(Base)";限定符的类型必须为"Derived"(或从中派生)

我理解这个错误的目的:它阻止访问兄弟类型的受保护成员。但是Base.Base(Base)显然不会在同级类型上调用!这只是不包括在规范中,还是我遗漏了一些不安全的原因?

编辑:Gah,成语是new Base(other.foo)而不是new Base(other)

为什么可以';t子类使用基类保护的构造函数创建新对象

语言规范的第3.5.2节和第3.5.3节详细说明了这一点,为了方便起见,我将发布3.5.2(它更短!),让您自己找到3.5.3。

直观地说,当访问成员M时评估步骤以确保允许访问:

  • 首先,如果M是在一个类型中声明的(与编译单元相反或命名空间)、编译时错误如果该类型不可访问,则发生
  • 然后,如果M是公共的,则允许访问
  • 否则,如果M在内部受到保护,则允许访问它发生在程序中M被声明,或者如果它发生在从中的类派生的类M被声明并发生通过派生类类型(§3.5.3)
  • 否则,如果M受到保护,则在发生访问时允许访问在M为的类内声明,或者如果它发生在类派生自其中M是通过声明和发生的派生类类型(§3.5.3)。
  • 否则,如果M是内部的,则在发生访问时允许访问在M所在的程序中声明
  • 否则,如果M是私有的,则在发生访问时允许访问在M是声明
  • 否则,类型或成员不可访问,并且会出现编译时错误发生

基本上,必须通过派生的实例来访问受保护的基成员。正如你所说,兄弟姐妹不能访问彼此的受保护成员,但语言规范也禁止孩子访问受保护的基础成员,除非引用是通过孩子。

如果您可以执行此操作,那么您可以始终平凡地调用任何允许从中派生的类的protected成员,即使未使用派生类。这完全颠覆了protected机制的安全性。

例如,假设我们从Base导出Derived,如上所述。如果规则不是原来的样子,您可以在Derived:中添加这样的方法

public static void CallProtectedMethod(Base baseInstance)
{
    baseInstance.ProtectedMethod();
}

现在任何人都可以这样调用它:

Derived.CallProtectedMethod(baseInstance);

而直接进近失败:

baseInstance.ProtectedMethod();

在这种情况下,baseInstance可能真的是Base类型,并且与Derived无关。为了防止这种情况,并确保protected方法保持protected,除非实例确实是Derived类型,否则通过其他实例调用这些方法是非法的。

同样,对于protected构造函数:

public static Base CreateProtectedBase()
{
    return new Base();
}

现在任何人都可以这样调用它:

var baseInstance = Derived.CreateProtectedBase();

而直接进近失败:

var baseInstance = new Base();

它不可访问,您可以查看此帖子了解详细信息:许多问题:受保护的构造函数

如果在派生中创建一个新的基对象,则无法通过该对象访问受保护的成员。试试这个代码

class Base
{
     protected int x;
}
class Derived : Base
{
    Base foo;
    void testMethod()
    {
        base.x = 5;
        foo.X = 5;// this throws an error 
    }
}

更改构造函数的访问说明符以使其工作,或者使用base.X 来代替创建基类(base)的对象