无法访问基类中的受保护成员

11

假设你有以下代码:

public abstract class MenuItem
    {
        protected string m_Title;
        protected int m_Level;
        protected MenuItem m_ParentItem;
        public event ChooseEventHandler m_Click;

        protected MenuItem(string i_Title, int i_Level, MenuItem i_ParentItem)
        {
            m_Title = i_Title;
            m_Level = i_Level;
            m_ParentItem = i_ParentItem;
        }
}

public class ContainerItem : MenuItem
    {
    private List<MenuItem> m_SubMenuItems;

    public ContainerItem(string i_Title, int i_Level, MenuItem i_ParentItem)
                            :base(i_Title, i_Level, i_ParentItem)
    {
        m_SubMenuItems = new List<MenuItem>();
    }

    public string GetListOfSubItems()
    {
        string subItemsListStr = string.Empty;

        foreach (MenuItem item in m_SubMenuItems)
        {
           item.m_Title = "test";  // Cannot access protected member the qualifier   
                                  must be of type 'Ex04.Menus.Delegates.ContainerItem' 

        }

        return subItemsListStr;
    }
}
  1. 我真的不理解这个错误背后的逻辑,我已经阅读了http://blogs.msdn.com/b/ericlippert/archive/2005/11/09/491031.aspx,但是根据 Protected 访问修饰符的定义,我仍然认为这完全是不合逻辑的。我认为它应该可以从定义它的同一类(MenuItem)以及所有派生类(ContainerItem等)中访问。

  2. 如果因为多态设计原因持有 MenuItem 的引用,那么如何访问受保护成员,比如 m_Title

2个回答

22

为什么会发生这种情况?

一个不容置疑的答案是“因为规范这样规定”:

 

基类中的protected成员只有在通过派生类类型进行访问时才能在派生类中访问。

但是让我们从背后探索这个限制。

解释

这里所发生的事情与Eric Lippert在你链接的博客文章中描述的情况相同。您的代码执行了类似于此代码的操作:

public abstract class MenuItem
{
    protected string m_Title;
}

public class ContainerItem : MenuItem
{
    void Foo()
    {
        var derivedItem = new ContainerItem();
        derivedItem.m_Title = "test"; // works fine

        var baseItem = (MenuItem)derived;
        baseItem.m_Title = "test"; // compiler error!
    }
}

这里的问题源于可能会发生的事实。暂时请忽略此示例使用方法而不是字段的事实——我们会回到它。


public abstract class MenuItem
{
    protected void Foo() {}
}

public class SomeTypeOfItem : MenuItem
{
    protected override void Foo() {}
}

public class ContainerItem : MenuItem
{
    void Bar()
    {
        var baseItem = (MenuItem)something;
        baseItem.Foo(); // #1
    }
}

看一下第一行代码:编译器如何知道baseItem实际上不是SomeTypeOfItem?如果是,你肯定不能访问Foo!所以,正如Eric所描述的那样,编译器无法静态地证明这个访问总是合法的,因此必须禁止这段代码。

请注意,在某些情况下,例如如果

baseItem = (MenuItem)new ContainerItem();

甚至

baseItem = (MenuItem)this;

编译器虽然有足够的信息来证明访问是合法的,但仍然不允许代码编译。我想这是因为编译器团队并不认为实现这样的特殊情况处理程序值得麻烦(我同情这种观点)。

但是......但是......

对于方法(和属性,它们实际上是方法),这都很好——那字段呢?下面这个怎么办:

public abstract class MenuItem
{
    protected string m_Title;
}

public class SomeTypeOfItem : MenuItem
{
    protected new string m_Title;
}

public class ContainerItem : MenuItem
{
    void Foo()
    {
        var baseItem = (MenuItem)something;
        baseItem.m_Title = "Should I be allowed to change this?"; // #1
    }
}

由于字段无法被覆盖,因此这里不应该存在任何歧义,并且代码应该编译并设置MenuItem.m_Title,而不管something的类型是什么。

实际上,我想不出任何技术原因导致编译器不能这样做,但无论如何都有一个很好的理由:一致性。Eric自己可能能够提供更丰富的解释。

那我该怎么办?

在保持对MenuItem引用的情况下,如何访问受保护的成员m_Title(由于多态设计原因)?

你根本不能这样做;你必须将成员设为internal(或public)。


@JavaSa:我编辑了答案,因为之前的版本不是很令人满意--请再看一遍。如果你通过“public”属性公开字段,那么这些字段绝对没有必要是“protected”,我认为这是糟糕的设计。 - Jon
谢谢,但我曾经说过成员变量不应该是公共的,并且它们始终应该是私有的。 我持有的列表引用了各种可能不同类型的 MenuItem 对象集合,而我想要做的就是能够从基类中获取它们已经继承的成员变量的值,无论它是哪个具体类型。我希望能够以多态的方式(基类视图)访问它们的值。 - JavaSa
1
@JavaSa 我猜你所说的“members”指的是字段,因为有很多原因需要将属性设置为公共的。在你的情况下,Title字段显然需要在类本身的范围之外使用,因此它应该是一个公共属性。如果你担心的是数据的实际设置,则将其设置为私有,而不是属性本身。 - James
1
@JavaSa:是的,“没有理由使用protected”意味着它们应该是private。当然,属性本身将是publicinternal,这取决于设计的合理性。 - Jon
好的回答,解释了为什么它不起作用。然而,我不明白他们为什么要选择这种方式。通常会有一个层次结构,在基类中存放希望保护的数据,而派生类型会进行协作,这些数据不希望公开。 - Ylisar
显示剩余2条评论

3

protected的意思是派生类可以访问它,但是派生类只能访问自己实例的属性。在你的示例中,你可以访问this.m_Title,因为它属于该实例本身,但你正尝试访问另一个实例的受保护成员(即列表m_SubMenuItems中的实例)。

您需要使用getter和setter方法以您正在尝试的方式来访问它。

希望这样更清楚:

class Foo {
    protected int x;

    public void setX(int x) {
        this.x = x;
    }
}

class Bar : Foo {
    Foo myFoo = new Foo();

    public void someMethod() {
        this.x = 5;    // valid. You are accessing your own variable
        myFoo.x = 5;   // invalid. You are attempting to access the protected
                       // property externally
        myFoo.setX(5); // valid. Using a public setter
    }
}

这是与其他答案相同的错误陈述。证明它是错误的。 - Jon
@Jon:你的证明与OP代码的条件不同。区别在于:你有Derived的实例,他有Base的实例! - Daniel Hilgarth
@DanielHilgarth:当然,这不是相同的代码。我的设计旨在表明,如果您拥有不同的实例,那么重要的是这些实例的静态类型,而不是实例本身。这里的答案说重要的是实例。 - Jon
1
@Jon:确切地说,静态类型很重要。这就是问题所在。他有一个实例列表,其静态类型为“Base”。这正是他无法访问受保护成员的原因。他可以将其强制转换为“Derived”,但前提是它确实是该类型的实例-而这是未知的。 - Daniel Hilgarth
@Jon:嗯,我认为我们都同意实例的声明类型是相关的,而不是实例本身。显然,我们都以不同的方式解释了“另一个实例”。对我来说,这隐含地包含了“其他实例”是静态类型Base的事实。对你的回答点赞。 - Daniel Hilgarth
显示剩余2条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接