【问题标题】:Why can't I override a private member in a nested class?为什么我不能覆盖嵌套类中的私有成员?
【发布时间】:2012-02-21 10:17:44
【问题描述】:

在 C# 中,嵌套类可以访问包含类的私有成员。

为什么我不能覆盖这些成员?还有为什么编译器会报错?

    private abstract class InheritanceTest
    {
        public virtual object Property
        {
            get { return null; }
            private set { }
        }

        public class Child : InheritanceTest
        {
            public override object Property
            {
                get { return null; }
                private set { base.Property = null; } // the base.Property = null statement here is just to show that there isn't an error message for accesing a parent private member.
            }
        }
    }

我得到的唯一错误信息是:

“Program.InheritanceTest.Child.Property.set”:无法覆盖继承的成员“Program.InheritanceTest.Property.set”,因为它没有标记为虚拟、抽象或覆盖

编译器显然出错了,因为整个属性都被标记为虚拟的。 get 方法可以覆盖继承的成员。

这是 C# 规范的一部分,只有错误消息是错误的吗?还是应该允许这样做?

我缺少规范的哪一部分? (还是缺少编译器?)

【问题讨论】:

标签: c# inheritance


【解决方案1】:

私有虚拟方法和属性不能被覆盖:

您不能将 virtual 修饰符与 static、abstract、 私有或覆盖修饰符。 virtual (C# Reference)

另见Can you override private virtual methods?

【讨论】:

    【解决方案2】:

    没有virtual private 这样的东西。因此,外部类定义的set 访问器不是virtual,因此您不能覆盖它。

    对于方法,规范明确禁止private virtual(C# 4 规范第 10.6 节):

    如果声明包含 private 修饰符,则声明不包含以下任何修饰符:virtualoverrideabstract

    关于属性(§10.7):

    关于修饰符的有效组合,属性声明遵循与方法声明(第 10.6 节)相同的规则。

    规范中唯一似乎与virtual 属性相关的部分,其中访问器之一是private(第10.7.5 节):

    virtual 属性声明指定属性的访问器是虚拟的。 virtual 修饰符适用于读写属性的两个访问器——不可能只有一个读写属性的访问器是虚拟的。

    这似乎与实际发生的情况相矛盾:只有非private 访问器是虚拟的。除非我遗漏了什么,否则我认为这要么是文档中的错误,要么是编译器中的错误。我为此创建了一个Connect bug,让我们看看微软怎么说。

    有关private virtual 方法的更多信息,请参阅this answer from Eric Lippert

    【讨论】:

    • +1 用于参考规范,但我认为该行文档是正确的。 virtual 被省略的地方是在非读写属性的情况下(只读或只写只有一个成员virtual)。请记住,如果 get 或 set 标记为 private,则它不再是 virtual,并且属性不再是“读写”,因此不符合规范中的那一行。
    • 仅仅因为其中一个访问器具有不同的可访问性并不意味着该属性不再是可读写的。或者至少我在规范中没有找到任何可以这么说的东西。
    • 啊,是的,我明白你的意思了,对不起,我从派生类的角度考虑“只读”和“只写”,而不是从基类本身的角度考虑。对不起。在那种情况下,是的,我认为你是对的,规范可能是错误的。
    【解决方案3】:

    根据定义,private 成员不可覆盖。 如果您希望 setter 可被覆盖,您可以将其标记为 protected 而不是 private

       private abstract class InheritanceTest
        {
            public virtual object Property
            {
                get { return null; }
                protected set { }
            }
    
            public class Child : InheritanceTest
            {
                public override object Property
                {
                    get { return null; }
                    protected set { base.Property = null; }
                }
            }
        }
    

    要更具体地回答您关于为什么的问题:

    了解当您的 C# 代码编译为 IL 代码时,实际上 1 个属性最终会产生 3 个东西。

    1. Property 属性本身。
    2. 一个名为get_Property() 的方法,它是getter。
    3. 一个方法名为set_Property(),它是setter。

    在您的代码中,您告诉 .NET “我想要一个 virtual 属性。然后它将访问级别级联到 getter 和 setter 方法。实际上,在 IL 代码中,属性不指定 virtual at全部。

    对于 C# 代码:

    public virtual object Property { get; set; }
    

    生成的IL代码为:

    .property instance object Property() { ... }
    
    .method public hidebysig newslot specialname virtual 
        instance object  get_Property() cil managed
        { ... }
    
    .method public hidebysig newslot specialname virtual 
        instance object  set_Property() cil managed
        { ... }
    

    请注意,publicvirtual 关键字适用于 getter 和 setter 方法,但不适用于属性本身。

    现在,通过将 C# 代码更改为:

    public virtual object Property { get; private set; }
    

    你已经告诉 .NET 你希望你的 getter 和 setter 方法是虚拟的......然而,然后它运行到private set,并且访问级别覆盖 publicvirtual 访问级别,用于 setter 方法。所以,生成的IL代码就变成了:

     .property instance object Property() { ... }
    
    .method public hidebysig newslot specialname virtual 
        instance object  get_Property() cil managed
        { ... }
    
    .method private hidebysig newslot specialname
        instance object  set_Property() cil managed
        { ... }
    

    请注意,现在 set_Property()private,不再是 virtual。在 .NET 中实际上不可能有 private virtual,因为它没有意义......这就像试图说“没有其他类可以看到这个......但是派生类可以覆盖这个东西,他们看不到或访问”这是没有意义的。派生类不能覆盖他们甚至看不到的东西。

    protected 关键字在这种情况下是合适的替代品,因为它告诉 .NET “只有我自己和派生类可以看到或访问这个,派生类可以覆盖这个属性。”

    所以我猜“简短”的答案应该是“因为 .NET 中的事情不能是 privatevirtual,所以编译器会采用你给它的更受限制的访问级别。

    另外,IMO 的错误信息非常正确。

    “Program.InheritanceTest.Child.Property.set”:无法覆盖继承的成员“Program.InheritanceTest.Property.set”,因为它没有标记为虚拟、抽象或覆盖

    请注意,它说的是'Program.InheritanceTest.Property.set',所以末尾的“.set”指的是最终的set_Property() 方法,而不是Property 属性。并且set_Property() 方法仅标记为private,因为.NET 编译器看到了这一点,并出于上述原因从该方法中删除了virtual。我想有一个编译器警告或者说“虚拟将被'set'忽略”是有道理的。

    希望这更有意义...

    【讨论】:

    • 是的,我已经这样做了,真正的问题是“为什么我必须这样做?”和“为什么错误信息没那么有用?”
    • @McKay - 我扩展了我的答案,试图解释“为什么”。
    猜你喜欢
    • 2020-10-30
    • 2020-08-02
    • 2011-12-04
    • 2012-09-24
    • 1970-01-01
    • 2017-03-03
    • 2011-07-09
    • 1970-01-01
    相关资源
    最近更新 更多