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

时间:2012-12-03 12:31:42

标签: c# inheritance protected

考虑您有以下代码:

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 Access修饰符的定义,我仍然认为它完全不合逻辑。 我认为它应该可以从定义它的同一个类MenuItem及其所有派生类中访问! (ContainerItem等)

  2. 如果持有对m_Title的引用(由于多态设计原因),您如何访问MenuItem等受保护成员?

2 个答案:

答案 0 :(得分:19)

为什么会这样?

无法与之争论的答案是“因为规范says so”:

  

可以在派生类中访问基类的protected成员   仅当通过派生类类型进行访问时才

但让我们在幕后探讨这种限制。

解释

这里发生的事情与Eric Lippert在您链接的博客文章中描述的相同。您的代码相当于this

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

看第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的类型是什么。

实际上,我无法想到编译器无法做到这一点的技术原因,但在任何情况下都有充分的理由:一致性。埃里克本人可能会提供更丰富的解释。

那我该怎么办?

  

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

你根本做不到;你必须让成员internal(或public)。

答案 1 :(得分: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
    }
}