除了这样,为啥我不能访问受 C# 保护的成员?

Posted

技术标签:

【中文标题】除了这样,为啥我不能访问受 C# 保护的成员?【英文标题】:Why can't I access C# protected members except like this?除了这样,为什么我不能访问受 C# 保护的成员? 【发布时间】:2010-10-08 17:41:43 【问题描述】:

这段代码:

abstract class C

    protected abstract void F(D d);


class D : C

    protected override void F(D d)  

    void G(C c)
    
        c.F(this);
    

产生这个错误:

无法通过“C”类型的限定符访问受保护的成员“C.F(D)”;限定符必须是“D”类型(或派生自它)

他们到底在想什么? (改变这条规则会破坏什么吗?)除了公开 F 之外,还有其他办法吗?


编辑:我现在明白为什么会这样了(感谢Greg),但我仍然对理性感到有些困惑;给定:

class E : C

    protected override void F(D d)  
  

为什么不应该 D 能够调用 E.F?


错误消息已被编辑,所以我可能在那里输入了一个错字。

【问题讨论】:

您的代码无法编译...看起来 D 应该是从 C 派生的,但它没有显示这一点。如果确实如此,则这些方法具有不同的可见性 D.F,因此不能覆盖 C.F.当您询问有关特定语言语义的问题时,您需要使代码准确。 为什么成员 F 应该是可访问的?显然 - 与任何参数/变量一样 - 您只会看到参数 c 的公共接口。 @divo:为什么它应该是可访问的?因为我认为没有令人信服的理由不应该这样做? @BCS:受保护方法背后的部分想法是让派生类决定应该使用父类的哪些能力。如果ReadableFoo 提供protected 成员来设置其属性,MutableFoo 可以从公共设置器中调用这些成员,而`ImmutableFoo` 可以避免这样做;如果ImmutableFoo 被密封并且不使用受保护的设置器,则不会使用这些设置器。如果MutableFoo 可以访问来自ReadableFoo 的任何派生的设置器,那么保护将丢失。 【参考方案1】:

“protected”关键字意味着只有一个类型和从该类型派生的类型才能访问该成员。 D 与 C 没有关系,因此无法访问该成员。

如果您希望能够访问该成员,您有几个选择

公开 将其设为内部。这将允许任何类型访问同一程序集中的成员(或其他程序集,如果您添加朋友的) 从 C 导出 D

编辑

C# 规范的第 3.5.3 节中提到了这种情况。

不允许这样做的原因是因为它允许跨层次调用。想象一下,除了 D 之外,还有另一个名为 E 的 C 基类。如果您的代码可以编译,它将允许 D 访问成员 EF 这种情况在 C# 中是不允许的(我相信 CLR,但我不是 100% 知道)。

EDIT2为什么不好

请注意,这是我的意见

现在允许这样做的原因是它很难推理类的行为。访问修饰符的目标是让开发人员准确控制谁可以访问特定方法。想象一下下面的类

sealed class MyClass : C 
  override F(D d)  ...  

考虑一下如果 F 是一个时间紧迫的函数会发生什么。根据当前的行为,我可以推断我的课程的正确性。毕竟只有两种情况会调用 MyClass.F。

    在 C 中调用它的位置 我在 MyClass 中显式调用它的地方

我可以检查这些调用,并就 MyClass 的功能得出一个合理的结论。

现在,如果 C# 确实允许跨层次保护访问,我无法做出这样的保证。完全不同的程序集中的任何人都可以从 C 中派生。然后他们可以随意调用 MyClass.F。这使得完全不可能推理我的课程的正确性。

【讨论】:

我明白你的观点(在edit2中),但我仍然不喜欢它。恕我直言,这给我买的不够。 “D 与 C 没有关系”,“如果您希望能够访问该成员......从 C 派生 D”。但在 OP 的帖子中:“class D : C”。 “完全不同的程序集中的任何人都可以从 C 中获得并派生”:是的,这就是继承的本质。如果您允许继承,那么继承您的类的任何人都可以编写与您设计函数的方式相反的函数,而 C# 允许跨程序集发生这种情况。这个答案是反对面向对象编程的论据。 这很聪明。他们如何能想到所有这些极端情况而不犯错误。现在我明白为什么它被阻止了,但这首先并不明显。 这种解释并没有更清楚地说明为什么继承的类不能在需要时调用基类型的成员。这就是“受保护”的全部意义所在。您不必公开某些内容以供派生类访问它。如果 D 不是从 C 派生的,那就毫无疑问了。我不明白为什么这个答案基于 OPs 示例以外的东西而被接受。【参考方案2】:

这不起作用的原因是因为 C# 不允许跨层次调用受保护的方法。假设有一个类E 也派生自C

  C
 / \
D   E

那么您尝试调用该方法的引用实际上可能是E 类型的实例,因此该方法可以在运行时解析为E.F。这在 C# 中是不允许的,因为 D 不能调用 E 的受保护方法,因为 E 在层次结构的另一个分支中,即

var d = new D();
var e = new E();
d.G(e); // oops, now this will call E.F which isn't allowed from D

这是有道理的,因为关键字 protected 表示成员“is accessible within its class and by derived class instances”,而 E.F 不是 D 的成员。

【讨论】:

这是一个有趣的观点。我一直认为 D.F 和 E.F 是 C.F 的特例,因此可以以全有或全无的方式访问。 “D 不能调用 E 的受保护方法”。假设CD 在一个程序集中,E 在另一个程序集中,Fprotected internal。然后F 成为Eprotected 成员,但D 可以调用它。因为从E 的角度来看Fprotected 但从D 的角度来看F 仍然是protected internal。这对我来说总是有点奇怪:)【参考方案3】:

即使 D 继承自 C,D 也无法访问 C 的受保护成员。 D 可以访问 D 的受保护(和私有!)成员,因此如果您将 D 的另一个实例而不是 C 放在那里,一切都会正常工作。但正如 Greg 所说,C 可能真的是完全不同的东西,因为编译器不知道 C 到底是什么,它必须阻止 D 访问 D 可能实际上无法访问的东西。

从 C# 编译器的角度解释这一点的一系列帖子:

Why Can't I Access A Protected Member From A Derived Class Why Can't I Access A Protected Member From A Derived Class, Part Two: Why Can I?

【讨论】:

+1 用于 Eric Lippert 链接,与往常一样,它比我希望的更能雄辩地解释事情(并且可能是我最初获取信息的地方!)。【参考方案4】:

为了理解为什么这种行为是有意义的,让我们考虑一下为什么我们在面向对象的编程语言中需要访问修饰符。我们需要它们限制可以使用特定类成员的范围。这反过来又简化了对用法的搜索。

总结一下:

要查找 public 成员的所有用法,需要搜索整个项目(对于独立开发人员使用的库,这还不够)李> 要查找 protected 成员的所有用法,需要搜索容器类及其所有子类 要查找 private 成员的所有用法,需要搜索 容器类

因此,如果编译器允许以所述方式从超类调用受保护方法,我们最终可能会像this answer 中所述那样跨层次调用受保护方法。在这种情况下,必须搜索定义该成员的最父类的所有子类。这将扩大范围。

PS。在 Java 中实现了相同的行为。

【讨论】:

另一种看待它的方式是,.NET 希望保证基类的受保护成员只会以它们允许的方式调用派生类[派生类可能会阻止子派生类访问受保护的通过定义与名称冲突的new 虚拟标识符来定义其父级的基本方法(至少通过反射除外)。【参考方案5】:

可以通过使用静态保护方法绕过此限制:

abstract class C

    protected abstract void F (D d);

    // Allows calling F cross-hierarchy for any class derived from C
    protected static void F (C c, D d)
    
        c.F(d);
    


class D : C

    protected override void F (D d)  

    void G (C c)
    
        // c.F(this);
        F(c, this);
    

从安全的角度来看,这并不完美(任何人都可以从C 派生),但是如果您只关心从类C 的公共接口中隐藏方法F,那么这个技巧可能是有用。

【讨论】:

【参考方案6】:

是的,这是可能的。我们很可能很快就会有这样的例子。

为此,您必须执行以下操作:

    继承默认表单 (EditAppointmentDialog) 并进行自定义(您甚至可以为此使用 winforms 设计器)。

公共部分类 CustomAppointmentEditDialog : EditAppointmentDialog 私人 RadComboBox cmbShowTimeAs = null;

    public CustomAppointmentEditDialog() 
     
        InitializeComponent(); 

        this.cmbShowTimeAs = this.Controls["cmbShowTimeAs"] as RadComboBox; 
     

    private void chkConfirmed_ToggleStateChanged(object sender, StateChangedEventArgs args) 
     
        this.cmbShowTimeAs.SelectedValue = (args.ToggleState == ToggleState.On) ? 
            (int)AppointmentStatus.Busy : (int)AppointmentStatus.Tentative; 
     
 

在上面的代码中,我添加了一个额外的复选框,如果未选中,则将约会的状态(显示时间)设置为 Tentative,如果选中,则将其设置为 Busy。访问组合框的奇怪方式是因为它目前是私有的。这将在即将发布的 2009 年第一季度版本中进行更改。

    订阅 RadScheduler 的 AppointmentEditDialogShowing 事件并将默认表单替换为您自定义的表单:

私人 IEditAppointmentDialog 约会EditDialog = null;

    protected override void OnLoad(EventArgs e) 
     
        base.OnLoad(e); 

        this.radScheduler1.AppointmentEditDialogShowing += new EventHandler<AppointmentEditDialogShowingEventArgs>(radScheduler1_AppointmentEditDialogShowing); 
     

    void radScheduler1_AppointmentEditDialogShowing(object sender, Telerik.WinControls.UI.AppointmentEditDialogShowingEventArgs e) 
     
        if (this.appointmentEditDialog == null) 
         
            this.appointmentEditDialog = new CustomAppointmentEditDialog(); 
         
        e.AppointmentEditDialog = this.appointmentEditDialog; 
     

我希望这会有所帮助。如果您还有其他问题,请随时给我回信。

【讨论】:

【参考方案7】:

简单地说:访问实例的受保护成员被视为公共访问,即使您尝试从派生类中这样做。因此,它被拒绝了。


这里和那里有很多答案,但没有一个让我明白“为什么我不能从孩子那里访问父类的受保护成员”。以上是我在阅读了这些令人困惑的答案后再次查看我的代码后所理解的。

例子:

class Parent

    protected int foo = 0;


// Child extends from Parent
class Child : Parent

    public void SomeThing(Parent p)
    
        // Here we're trying to access an instance's protected member.
        // So doing this...
        var foo = p.foo;
    


// (this class has nothing to do with the previous ones)
class SomeoneElse

    public void SomeThing(Parent p)
    
        // ...is the same as doing this (i.e. public access).
        var foo = p.foo++;
    

您可能认为您可以访问p.foo,因为您在一个子类中,但您是从一个实例访问它,这就像一个公共访问,所以它被拒绝了。

您可以从类内访问protected 成员,而不是从实例(是的,我们知道这一点):

class Child : Parent

    public void SomeThing()
    
        // I'm allowed to modify parent's protected foo because I'm
        // doing so from within the class.
        foo++;
    

最后,为了完整起见,只有在同一个类中才能访问实例的 protected 甚至 private 成员:

class Parent

    protected int foo = 0;

    private int bar = 0;

    public void SomeThing(Parent p)
    
        // I'm allowed to access an instance's protected and private
        // members because I'm within Parent accessing a Parent instance
        var foo = p.foo;
        p.bar = 3;
    

【讨论】:

这在 C# 中有点笨,访问父类的实例不应该像公共访问...

以上是关于除了这样,为啥我不能访问受 C# 保护的成员?的主要内容,如果未能解决你的问题,请参考以下文章

为啥我不能访问派生构造函数的成员初始化列表中继承的受保护字段?

为啥我不能访问子类中的受保护变量?

受保护的成员不能通过指针或对象 c++ 访问

为啥派生类的朋友不能使用受保护的成员?

通过基类变量访问的 C# 受保护成员 [重复]

在 C# 中,我们可以对接口使用受保护的访问修饰符吗?