为啥子类不能使用基类受保护的构造函数创建新对象?

Posted

技术标签:

【中文标题】为啥子类不能使用基类受保护的构造函数创建新对象?【英文标题】:Why can't subclasses create new objects with a base class protected constructor?为什么子类不能使用基类受保护的构造函数创建新对象? 【发布时间】:2011-09-07 13:31: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)”;限定符必须是“派生”类型(或派生自它)

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

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

【问题讨论】:

@Alastair Pitts -- 是的......你是对的 【参考方案1】:

语言规范的 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 的类型内 声明。 否则,类型或成员不可访问,并出现编译时错误 发生。

基本上,访问基类的受保护成员必须通过派生类的实例。正如您所说,兄弟姐妹无法访问彼此的受保护成员,但语言规范还禁止孩子访问基础的受保护成员,除非通过孩子进行引用。

【讨论】:

当然,对于非构造函数成员,我明白这一点,但据我所知,构造函数成员仅由new 的实现调用 - 所以我看不出这怎么可能是 un-类型安全。 @Simon,我不会假装我能想到这可能会爆炸的极端情况,但我不怀疑 somebody 能想出办法.我只能说你可以通过提供受保护的方法而不是受保护的构造函数来解决这个限制。 protected Base CreateBase(Base other) 可以通过派生子调用。 @Simon:想在任何库中调用protected 构造函数吗?只需写十行代码!这就是为什么它是非法的。 @Rick:我对protected 的解释是为了帮助实施类型安全并更容易实施 Liskov 可替换性,如果我想防止其他程序集访问成员以确保安全,我会想到internalprivate。如果这不是预期用途,那么这就是我的答案! @Simon Buchan:声明构造函数受保护的目的不是限制谁可以创建实例,而是可以创建什么样的实例。将类“foo”构造函数声明为“protected”可防止它被用于创建“foo”的实例,同时允许它用于创建从“foo”派生的类型。除了从派生类构造函数链接之外,以任何方式调用“foo”的任何构造函数都会产生“foo”的实例,而不是派生类的实例——这正是“受保护”声明应该防止的。 【参考方案2】:

它无法访问,您可以查看此帖子了解详细信息:Many Questions: Protected Constructors

【讨论】:

感谢您的链接,因此受保护的构造函数被阻止...作为安全保护?这很奇怪。 是的........即使我不知道这一点,但读完后我看到了硬币的另一面 标记为答案,因为它链接了推理。如果有人(比我有更多时间!)为其他人编辑了这个答案的推理,那就太好了。【参考方案3】:

如果您在派生中创建新的 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)的对象

【讨论】:

【参考方案4】:

如果你可以这样做,那么你可以总是简单地调用任何允许你从它派生的类的 protected 成员,即使派生类是未使用。这完全颠覆了protected机制的安全性。

例如,假设我们从BaseDerived 派生如上。如果规则不是原来的样子,您可以在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();

【讨论】:

以上是关于为啥子类不能使用基类受保护的构造函数创建新对象?的主要内容,如果未能解决你的问题,请参考以下文章

获取指向基类受保护成员函数的函数指针

类方法详解

为啥子类继承私有字段?

关于类继承的构造与析构调用分析

C++入门派生类和基类的构造/析构函数关系

请问把基类构造函数声明为protected有啥好处呢(抽象基类)