C#子类中的受保护属性是否应隐藏对父级公共属性的访问权限?

时间:2010-05-16 20:09:30

标签: c# mono

我有以下代码:

public class Parent
{
    public string MyField { get; set; }
}

public class Child : Parent
{
    protected new int MyField { get; set; }
}

我尝试使用以下方式访问:

static void Main(string[] args)
{
    Child child = new Child();
    child.MyField = "something";
}

Visual Studio 2008编译时没有注释,但在Mono(2.4.2,Ubuntu)下我收到错误消息

'HideTest.Child.MyField' is inaccessible due to its protection level (CS0122)

一个实现或其他实现更符合标准吗?

编辑:感谢所有指出糟糕设计的人。不幸的是,它是第三方库并且显着地改变它是不切实际的。

7 个答案:

答案 0 :(得分:25)

来自ECMA-334(C#规范)§10.7.1.2:

  

新成员的声明仅在新成员的范围内隐藏继承的成员。

您可以通过在Microsoft的实现上运行此测试来查看此行为。

using System;
using NUnit.Framework;

namespace ScratchPad
{
    [TestFixture]
    public class Class1
    {
        [Test]
        public void InheritanceHiding()
        {
            var b = new Base();
            var d = new Derived();

            var baseSomeProperty = b.SomeProperty;
            var derivedSomeProperty = d.SomeProperty;

            b.GetSomeProperty();
            d.GetSomeProperty();
        }
    }

    public class Base
    {
        public string SomeProperty
        {
            get
            {
                Console.WriteLine("Getting Base.SomeProperty");
                return "Base.SomeProperty";
            }
        }

        public string GetSomeProperty()
        {
            return SomeProperty;
        }
    }

    public class Derived : Base
    {
        protected new int SomeProperty
        {
            get
            {
                Console.WriteLine("Getting Derived.SomeProperty");
                return 3; //Determined by random roll of the dice.
            }
        }

        public new int GetSomeProperty()
        {
            return SomeProperty;
        }
    }
}

将输出:

Getting Base.SomeProperty    //(No Controversy)  
Getting Base.SomeProperty    //(Because you're calling from public scope and the new member is in protected scope, there is no hiding)  
Getting Base.SomeProperty    //(No Controversy)  
Getting Derived.SomeProperty //(Now because you're calling from protected scope, you get the protected member).

因此,您从Main()访问的属性应该是基类属性(在MS.NET中),而不是派生属性(如在Mono中),因为新的派生成员只隐藏受保护范围内的“旧”基础成员。

根据规范,Mono在这里做错了。

答案 1 :(得分:18)

杰森的回答是正确的,但他要求证明这种行为的正当性。 (即隐藏方法仅隐藏在隐藏方法的范围内。)

有许多可能的理由。一个特别是this is yet another way in which the design of C# mitigates the Brittle Base Class problem

FooCorp制作Foo.DLL:

public class Foo
{
    public object Blah() { ... }
}

BarCorp制作Bar.DLL:

public class Bar : Foo
{
    // stuff not having to do with Blah
}

ABCCorp制作ABC.EXE:

public class ABC
{
    static void Main()
    {
        Console.WriteLine((new Bar()).Blah());
    }
}

现在BarCorp说:“你知道,在我们的内部代码中,我们可以保证Blah只返回字符串,这要归功于我们对派生实现的了解。让我们在内部代码中利用这个事实。”

public class Bar : Foo
{
    internal new string Blah()
    {
        object r = base.Blah();
        Debug.Assert(r is string);
        return (string)r;
    }
}

ABCCorp选择了一个新版本的Bar.DLL,它有一些阻止它们的bug修复。 他们的构建是否会因为调用Blah(Bar 上的内部方法)而中断?当然不是。那将是可怕的。此更改是私有实现详细信息,应该在Bar.DLL之外不可见

答案 2 :(得分:5)

一般来说,C#的.NET实现应该被认为是“佳能”。来自new Modifier的文档:

  

在类或结构中引入的常量,字段,属性或类型使用同名隐藏所有基类成员。< / p>

......根据这个定义,似乎Mono实现更正确。它应该隐藏MyField类中Parent的实现,因此只能使用int MyField类中的Child签名访问它。

答案 3 :(得分:3)

序曲:此代码很疯狂。如果您的应用中确实有这样的代码,请立即修复。要么让它们受到保护,要么两者都公开!

关于错误:CLR在处理这样的事情时有很多非常奇怪的“边缘情况”规则。寻找这类东西的最佳地方通常是Eric Lippert's blog

但是,在我看来,在我看来,单声道看起来实际上是在做更明智的事情。


再看一下,一旦你考虑到“幕后”的东西,C#one会更有意义。

MSIL中的属性不是“第一类”。 C#或VB中的属性只是编译成get和set方法(编译器还在某个地方粘贴一个属性用于簿记)。

int MyField { get; set; }实际上会为两种方法生成MSIL:

void set_MyField(int value);
int get_MyField();

现在,鉴于您的new方法具有不同的类型,您最终会得到以下两种setter方法。

void set_MyField(int value);
void set_MyField(string value);

当你致电x.MyField = "string"时,你只是在调用其中一种方法。然后归结为正常的方法重载方案。有两个同名的方法采用不同的参数是完全有效的,所以编译器只需选择一个字符串并继续它的快乐方式。

所以是的。如果你知道内部是如何工作的话,C#one是有意义的,如果你没有,那么Mono更有意义。

哪一个“更正确”?问Eric Lippert: - )

答案 4 :(得分:2)

只需加上我的2美分)这是一个单声道错误,here就是描述。

答案 5 :(得分:0)

恕我直言,区别在于MS.NET识别MyField的类型字符串并设置Parent属性的值,而在Mono中只是尝试访问MyField中的Child } class。

答案 6 :(得分:0)

你正在通过孩子无法通过基类提供的东西。你可以尝试,但它实际上不会做任何事情。人们总能做到这一点:

Parent child = new Child();

并调用该方法。因此,如果您希望隐藏字段,请声明一个新字段并将继承的字段保持公开。