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

问题描述 投票:37回答:7

这段代码:

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?


编辑错误消息,所以我可能在那里输入错字。

c# override protected access-modifiers
7个回答
17
投票

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

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

  • 把它公之于众
  • 把它做成内部的。这将允许任何类型访问同一程序集中的成员(或者如果你添加了朋友,则允许其他程序集)
  • 从C中导出D.

编辑

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

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

EDIT2为什么这很糟糕

警告,这是我的看法

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

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

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

  1. 它在C中被调用的地方
  2. 我在MyClass中显式调用它的位置

我可以检查这些调用,并得出关于MyClass如何运作的合理结论。

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


38
投票

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


11
投票

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

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


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


1
投票

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

总结一下:

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

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

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


1
投票

简单地说:访问实例的受保护成员将被视为公共访问,即使您尝试在派生类中执行此操作也是如此。因此,它被否定了。


这里和那里有很多答案,但是他们都没有向我说明“为什么我不能从孩子那里访问父类的受保护成员”。在阅读了这些令人困惑的答案后再次查看我的代码后,我就明白了。

例:

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

0
投票

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

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

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

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事件,并用您自定义的表单替换默认表单:

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

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

© www.soinside.com 2019 - 2024. All rights reserved.