【问题标题】:Cannot access protected member in base class [duplicate]无法访问基类中的受保护成员 [重复]
【发布时间】:2012-11-20 22:09:20
【问题描述】:

假设您有以下代码:

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

【问题讨论】:

    标签: c# inheritance protected


    【解决方案1】:

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

    【讨论】:

    • 这是与其他答案相同的错误陈述。 Proof 是假的。
    • @Jon:您的证明没有在与 OP 代码相同的条件下运行。区别在于:你有Derived的实例,他有Base的实例!
    • @DanielHilgarth:当然不是同一个代码。我的设计是为了表明您是否有不同的实例并不重要,重要的是这些实例的静态类型。这里的答案说重要的是实例。
    • @Jon:没错,静态类型很重要。这就是重点。他有一个静态类型为Base 的实例列表。这就是他无法访问受保护成员的原因。他可以将其转换为Derived,但只有它确实是该类型的一个实例 - 这是未知的。
    • @DanielHilgarth:请看我自己的回答。没有比这更好的解释了。
    【解决方案2】:

    为什么会这样?

    无法反驳的答案是“因为规范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)。

    【讨论】:

    • 绝妙的答案!我想为每个受保护的成员添加公共属性更合乎逻辑,你怎么看?公共成员与具有公共属性的私有或受保护成员之间有区别吗?
    • @JavaSa:我编辑了答案,因为以前的版本不太令人满意——请再看看。如果您通过public 属性公开字段,则绝对没有理由将字段设置为protected,我会认为这是糟糕的设计。
    • 谢谢,但是,我曾经告诉过成员不能是公开的,并且他们总是应该是私有的。我持有的列表引用了多种不同MenuItems 的潜在多样化集合,我想要的只是能够从基类中获取它们已经继承的成员的值,无论它是哪种具体类型。我希望能够在以多态方式(基类视图)查看它们的同时达到它们的价值
    • @JavaSa 我假设“members”是指字段,因为有很多原因应该公开属性。在您的场景中,Title 字段显然在类本身的范围之外是必需的,因此它应该是一个公共属性。如果您关心的是数据的实际设置,则将其设为私有,而不是属性本身。
    • @JavaSa:是的,“没有理由成为protected”意味着他们应该是private。属性本身当然是publicinternal,这取决于设计的意义。
    猜你喜欢
    • 1970-01-01
    • 2014-01-26
    • 2014-02-22
    • 2019-01-20
    • 2016-10-01
    • 2012-08-29
    • 2022-01-06
    • 2016-04-07
    相关资源
    最近更新 更多