8

考虑您有以下代码:

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

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

4

2 回答 2

20

为什么会这样?

一个无法争论的答案是“因为规范是这样说的”:

仅当通过派生类类型进行访问时,才能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
    }
}

查看第 1 行:编译器如何知道它baseItem实际上不是 a 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是什么,代码都应该编译和设置。

事实上,我想不出编译器不能这样做的技术原因,但无论如何都有一个很好的理由:一致性。埃里克本人可能能够提供更丰富的解释。

那么我能做什么呢?

在持有对 MenuItem 的引用的同时,您将如何访问像 m_Title 这样的受保护成员(由于多态性设计原因)?

您根本无法做到这一点;你必须使成员internal(或public)。

于 2012-12-03T12:56:14.593 回答
3

protected确实意味着派生类可以访问它,但是,派生类可以访问它自己实例的属性。在您的示例中,您可以访问this.m_Title,因为它属于实例本身,但您正在尝试访问另一个实例的受保护成员(即 list 中的实例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
    }
}
于 2012-12-03T12:36:22.960 回答