这段代码:
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?
编辑错误消息,所以我可能在那里输入错字。
“protected”关键字意味着只有从该类型派生的类型和类型才能访问该成员。 D与C无关,因此无法访问该成员。
如果您希望能够访问该成员,则有几个选项
编辑
这种情况在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.
我可以检查这些调用,并得出关于MyClass如何运作的合理结论。
现在,如果C#允许跨层次保护访问,我就不能做出这样的保证。任何完全不同的程序集中的任何人都可以来自C.然后他们可以随意调用MyClass.F.这使我完全无法推断出我班级的正确性。
这不起作用的原因是因为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继承自C,D也无法访问C的受保护成员。 D可以访问D的受保护(和私有!)成员,所以如果你把D的另一个实例放在那里而不是C,一切都会工作。但正如格雷格所说,C可能真的是完全不同的东西,并且由于编译器不知道C究竟是什么,它必须阻止D访问D实际上无法访问的东西。
一系列帖子从C#编译器的角度解释了这一点:
使用静态保护方法可以绕过此限制:
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
,这个技巧可能会有用。
要理解为什么这种行为有意义,让我们考虑为什么在面向对象的编程语言中我们需要访问修饰符。我们需要它们来限制可以使用特定类成员的范围。而这反过来又简化了对用法的搜索。
总结一下:
因此,如果编译器允许以所描述的方式从超类调用受保护的方法,那么我们最终可以使用this answer中描述的受保护方法的跨层次调用。在这种情况下,人们必须搜索定义该成员的最多父类的所有子项。这会增加范围。
PS。 Java中实现了相同的行为。
简单地说:访问实例的受保护成员将被视为公共访问,即使您尝试在派生类中执行此操作也是如此。因此,它被否定了。
这里和那里有很多答案,但是他们都没有向我说明“为什么我不能从孩子那里访问父类的受保护成员”。在阅读了这些令人困惑的答案后再次查看我的代码后,我就明白了。
例:
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;
}
}
对的,这是可能的。我们很可能很快会有这样一个例子。
为此,您必须执行以下操作:
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年第一季度发布中进行更改。
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;
}
我希望这有帮助。如果您有其他问题,请随时给我回信。