42

这段代码:

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”类型的限定符访问受保护的成员“CF(D)”;限定符必须是“D”类型(或派生自它)

他们到底在想什么?(更改该规则会破坏某些东西吗?)除了公开 F 之外,还有其他方法吗?


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

class E : C
{
    protected override void F(D d) { }
}  

为什么D不能调用 EF?


错误消息已编辑,因此我可能在其中输入了错字。

4

7 回答 7

45

这不起作用的原因是因为 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意味着成员“可在其类中和派生类实例中访问”,而 EF 不是 D 的成员。

于 2009-02-19T23:34:36.467 回答
18

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

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

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

编辑

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

不允许这样做的原因是因为它允许跨层次结构调用。想象一下,除了 D 之外,还有另一个名为 E 的 C 基类。如果您的代码可以编译,它将允许 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。这使得完全不可能推理我的课程的正确性。

于 2009-02-19T23:29:58.707 回答
12

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

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

于 2009-02-19T23:43:08.067 回答
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),但是如果您只关心F从类的公共接口中隐藏方法,那么C这个技巧可能很有用。

于 2014-06-27T17:08:52.807 回答
3

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


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

例子:

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;
    }
}
于 2019-03-17T04:32:07.020 回答
1

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

总结一下:

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

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

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

于 2010-04-20T20:15:06.840 回答
0

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

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

  1. 继承默认表单 (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; 
    } 
} 

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

  1. 订阅 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; 
    } 

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

于 2016-02-02T13:32:47.420 回答