¿Debería una propiedad protegida en una clase hija de C# ocultar el acceso a una propiedad pública en el padre?


Tengo el siguiente código:

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

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

Intento acceder a esto con:

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

Visual studio 2008 compila esto sin comentarios, pero bajo Mono (2.4.2, Ubuntu) recibo el mensaje de error

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

¿Es una implementación u otra más compatible con el estándar aquí?

Edit: Gracias a todas las personas que han señalado el mal diseño. Desafortunadamente, es una biblioteca de terceros y cambiarla significativamente no es práctico.

 29
Author: Tim Martin, 2010-05-17

7 answers

De ECMA-334 (la especificación C#) §10.7.1.2:

Una declaración de un nuevo miembro oculta un miembro heredado solo dentro del alcance del nuevo miembro.

Puede ver este comportamiento ejecutando esta prueba en la implementación de 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;
        }
    }
}

Que producirá:

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).

Así que la propiedad a la que está accediendo desde su Main() debe ser la propiedad de clase base (como está en MS.NET), no la propiedad derivada (como en Mono), porque el nuevo derivado miembro solo oculta el miembro base 'antiguo' en el ámbito protegido.

Mono está haciendo algo mal aquí de acuerdo con la especificación.

 25
Author: Jason Punyon,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-05-16 21:38:56

La respuesta de Jason es correcta, pero pide una justificación de este comportamiento. (Es decir, que un método de ocultación solo se oculta dentro del alcance del método de ocultación.)

Hay una serie de justificaciones posibles. Una en particular es que esta es otra forma en la que el diseño de C# mitiga el problema de la Clase Base Frágil.

FooCorp hace Foo.DLL:

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

BarCorp hace Barra.DLL:

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

ABCCorp hace ABC.EXE:

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

Ahora BarCorp dice: "Sabes, en nuestro código interno podemos garantizar que Blah solo devuelve cadenas gracias a nuestro conocimiento de nuestra implementación derivada. Aprovechemos ese hecho en nuestro código interno."

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

ABCCorp recoge una nueva versión de Bar.DLL que tiene un montón de correcciones de errores que los están bloqueando. ¿Deberían romper su construcción porque tienen una llamada a Blah, un método interno en Bar? Por supuesto que no. Eso sería terrible . Este cambio es privado detalle de implementación que debería ser invisible fuera de Bar.DLL.

 18
Author: Eric Lippert,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-05-17 21:34:36

En general, la implementación de.NET de C# probablemente debería considerarse "canon". De la documentación sobre el nuevo modificador :

Una constante, campo, propiedad, o tipo introducido en una clase o estructura oculta todos los miembros de la clase base con el mismo nombre .

... parece que la implementación Mono es más correcta dada esta definición. Debería estar ocultando la implementación de {[0] } en la clase Parent, y por lo tanto solo debe ser accesible con la firma int MyField de la clase Child.

 5
Author: James Kolpack,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-05-16 20:32:51

Preludio: Este código es una locura. Si realmente tienes código en tu aplicación como este, arréglalo ahora. Ya sea que sean protegidos o ambos pública!

Con respecto al error: El CLR tiene muchas reglas de 'caso de borde' realmente extrañas para lidiar con cosas como esta. El mejor lugar para buscar este tipo de cosas suele ser El blog de Eric Lippert.

Al decir eso, sin embargo, parece que mono en realidad está haciendo lo más sensato aquí en mi opinión.


El segundo vistazo, el C # uno tiene más sentido una vez que el factor en el 'detrás de las escenas' cosas.

Las propiedades no son de "primera clase" en MSIL. Una propiedad en C# o VB simplemente se compila a un método get y set (el compilador también pega un atributo en algún lugar para la contabilidad).

int MyField { get; set; } en realidad producirá MSIL para dos métodos:

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

Ahora, dado que su método new tiene un tipo diferente, terminará con los siguientes 2 métodos setter.

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

Cuando llamas x.MyField = "string" solo estás llamando a uno de esos métodos. Esto se reduce a un escenario de sobrecarga de método normal. Es perfectamente válido tener dos métodos con el mismo nombre que toman diferentes parámetros, por lo que el compilador solo seleccionará la cadena uno y continuará su camino feliz.

Así que sí. El de C# tiene sentido si sabes cómo funcionan las funciones internas, el Mono tiene más sentido si no lo sabes.

Que es "más correcto"? Pregunta a Eric Lippert: -)

 3
Author: Orion Edwards,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-05-16 20:49:14

Simplemente agregando mis 2 centavos) Eso es un error Mono, aquí es la descripción.

 2
Author: n535,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-06-15 07:51:26

En mi humilde opinión la diferencia es que MS.NET reconoce el tipo string para MyField y establece el valor de la propiedad Parent y en Mono solo intenta acceder a MyField en la clase Child.

 0
Author: Incognito,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-05-16 20:21:15

Está haciendo que algo que está disponible a través de la clase base no esté disponible a través del hijo. Puedes intentarlo, pero en realidad no hará nada. La gente siempre puede hacer esto:

Parent child = new Child();

Y llame al método. Por lo tanto, si desea que el campo esté oculto, declare uno nuevo y mantenga público el heredado.

 0
Author: Jouke van der Maas,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-05-16 20:30:50