为什么我不能访问C#受保护的成员,除非像这样?

时间:2009-02-19 23:21:06

标签: c# override protected access-modifiers

此代码:

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?


编辑错误消息,因此我可能会在其中输入拼写错误。

7 个答案:

答案 0 :(得分:36)

这不起作用的原因是因为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的成员。

答案 1 :(得分:17)

“protected”关键字表示只有从该类型派生的类型和类型才能访问该成员。 D与C无关,因此无法访问该成员。

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

  • 公开
  • 将其设为内部。这将允许任何类型访问同一程序集中的成员(或添加朋友时的其他程序集)
  • 从C
  • 派生D.

修改

这个场景在C#规范的第3.5.3节中提到。

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

EDIT2 为什么这很糟糕

警告,这是我的意见

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

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

考虑如果F是一个有时间关键的功能会发生什么。根据目前的行为,我可以推断出我班级的正确性。毕竟只有两种情况会调用MyClass.F.

  1. 在C
  2. 中调用的位置
  3. 我在MyClass中明确调用它的地方
  4. 我可以检查这些调用,并得出关于MyClass如何运作的合理结论。

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

答案 2 :(得分:11)

即使D继承自C,D也无法访问C的受保护成员。 D可以访问D的受保护(和私有!)成员,所以如果你把D的另一个实例放在那里而不是C,一切都会工作。但正如格雷格所说,C可能真的是完全不同的东西,并且由于编译器不知道C究竟是什么,它必须阻止D访问D实际上无法访问的东西。

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

答案 3 :(得分:2)

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

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派生),但如果您关心的是从类{{1}的公共接口隐藏方法F这个技巧可能很有用。

答案 4 :(得分:1)

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

总结:

  • 查找公众成员的所有用法,需要搜索整个项目(对于独立开发者使用的库,这还不够)
  • 查找受保护成员的所有用法,需要搜索容器类及其所有子类
  • 查找私有成员的所有用法,需要搜索容器类

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

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

答案 5 :(得分:0)

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

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

  1. 继承默认表单(EditAppointmentDialog)并进行自定义(您甚至可以使用winforms设计器)。
  2. public partial class CustomAppointmentEditDialog:EditAppointmentDialog     {         private 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; 
        } 
    } 
    

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

    1. 订阅RadScheduler的AppointmentEditDialogShowing事件,并将默认表单替换为您自定义的表单:
    2. private IEditAppointmentDialog appointmentEditDialog = 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; 
          } 
      

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

答案 6 :(得分:0)

简而言之:即使您尝试从派生类中进行访问,也将访问实例的成员视为公开访问。因此,它被拒绝了。


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

示例:

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;
    }
}