Esto es Esparta, ¿o no?

121

La siguiente es una pregunta de entrevista. Se me ocurrió una solución, pero no estoy seguro de por qué funciona.


Pregunta:

Sin modificar la Spartaclase, escriba un código que MakeItReturnFalsedevuelva false.

public class Sparta : Place
{
    public bool MakeItReturnFalse()
    {
        return this is Sparta;
    }
}

Mi solución: (SPOILER)

public class Place
{
public interface Sparta { }
}

Pero ¿por qué no Spartaen MakeItReturnFalse()referencia a {namespace}.Place.Spartasu lugar de {namespace}.Sparta?

budi
fuente
55
¿Podría agregar Alerta de Spoiler o algo a la Solución, por favor? Estoy tan decepcionado que no tengo la oportunidad de resolverlo por mi cuenta. La pregunta es realmente asombrosa.
Karolis Kajenas
1
Inicialmente incluí etiquetas de spoiler, sin embargo, la comunidad la editó varias veces durante la vigencia de esta publicación. Lo siento por eso.
budi
1
No me gusta el título de esta publicación; es más apto para codegolf.SE. ¿Podemos cambiarlo por algo que realmente describa la pregunta?
Supuhstar
3
Lindo rompecabezas Pregunta de entrevista terrible, pero lindo rompecabezas. Ahora que sabe cómo y por qué funciona esto, debería probar
Eric Lippert

Respuestas:

117

Pero ¿por qué no Spartaen MakeItReturnFalse()referencia a {namespace}.Place.Spartasu lugar de {namespace}.Sparta?

Básicamente, porque eso es lo que dicen las reglas de búsqueda de nombres. En la especificación C # 5, las reglas de nomenclatura relevantes están en la sección 3.8 ("Espacio de nombres y nombres de tipo").

Los primeros dos puntos, truncados y anotados, se leen:

  • Si el nombre del espacio de nombres o tipos es de la forma Io de la forma I<A1, ..., AK> [entonces K = 0 en nuestro caso] :
    • Si K es cero y el nombre del espacio de nombres o tipos aparece dentro de una declaración de método genérico [no, no hay métodos genéricos]
    • De lo contrario, si el espacio de nombres o nombres de tipo aparece dentro de una declaración de tipo, entonces para cada tipo de instancia T (§10.3.1), comenzando con el tipo de instancia de esa declaración de tipo y continuando con el tipo de instancia de cada clase adjunta o declaración de estructura (si existe):
      • Si Kes cero y la declaración de Tincluye un parámetro de tipo con nombre I, entonces el espacio de nombres o nombre de tipo se refiere a ese parámetro de tipo. [No]
      • De lo contrario, si el espacio de nombre o nombre de tipo aparece dentro del cuerpo de la declaración de tipo, y T cualquiera de sus tipos base contiene un tipo accesible anidado que tiene parámetros de nombre Iy Ktipo, entonces el nombre de espacio de tipo o nombre se refiere a eso tipo construido con los argumentos de tipo dados. [¡Bingo!]
  • Si los pasos anteriores no tuvieron éxito, entonces, para cada espacio de nombres N, comenzando con el espacio de nombres en el que se produce el espacio de nombres o nombres de tipos, continuando con cada espacio de nombres adjunto (si corresponde) y terminando con el espacio de nombres global, se evalúan los siguientes pasos hasta que se localice una entidad:
    • Si Kes cero y Ies el nombre de un espacio de nombres en N, entonces ... [Sí, eso tendría éxito]

Entonces, ese último punto es lo que recoge la Sparta clase si el primer punto no encuentra nada ... pero cuando la clase base Placedefine una interfaz Sparta, se encuentra antes de considerar la Spartaclase.

Tenga en cuenta que si hace que el tipo anidado sea Place.Spartauna clase en lugar de una interfaz, aún se compila y regresa false, pero el compilador emite una advertencia porque sabe que una instancia de Spartanunca será una instancia de la clase Place.Sparta. Del mismo modo, si mantiene Place.Spartauna interfaz pero hace la Spartaclase sealed, recibirá una advertencia porque ninguna Spartainstancia podría implementar la interfaz.

Jon Skeet
fuente
2
Otra observación aleatoria: Usando la Spartaclase original , this is Placeretornos true. Sin embargo, agregar public interface Place { }a la Spartaclase hace this is Placeque regrese false. Hace girar mi cabeza.
budi
@budi: Correcto, porque nuevamente, la viñeta anterior se encuentra Placecomo la interfaz.
Jon Skeet
22

Al resolver un nombre a su valor, la "cercanía" de la definición se utiliza para resolver ambigüedades. Cualquier definición que sea "más cercana" es la que se elige.

La interfaz Spartase define dentro de una clase base. La clase Spartase define en el espacio de nombres que contiene. Las cosas definidas dentro de una clase base están "más cerca" que las cosas definidas en el mismo espacio de nombres.

Servy
fuente
1
E imagine si la búsqueda de nombres no funciona de esta manera. Luego, el código de trabajo que contiene una clase interna se rompería si alguien agregara una clase de nivel superior con el mismo nombre.
dan04
1
@ dan04: Pero en cambio, el código de trabajo que no contiene una clase anidada se rompe si alguien agrega una clase anidada con el mismo nombre que una clase de nivel superior. Por lo tanto, no es exactamente un escenario total de "ganar".
Jon Skeet
1
@ JonSkeet Yo diría que agregar una clase anidada de este tipo es un cambio en un área en la que el código de trabajo tiene razones razonables para verse afectado y estar atento a los cambios. Agregar una clase de nivel superior totalmente no relacionada se elimina mucho más.
Angew ya no está orgulloso de SO
2
@ JonSkeet ¿No es ese el problema de Brittle Base Class en una forma ligeramente diferente?
João Mendes
1
@ JoãoMendes: Sí, más o menos.
Jon Skeet
1

Hermosa pregunta! Me gustaría agregar una explicación un poco más larga para aquellos que no hacen C # a diario ... porque la pregunta es un buen recordatorio de los problemas de resolución de nombres en general.

Tome el código original, ligeramente modificado de las siguientes maneras:

  • Imprimamos los nombres de los tipos en lugar de compararlos como en la expresión original (es decir return this is Sparta).
  • Definamos la interfaz Athenaen la Placesuperclase para ilustrar la resolución del nombre de la interfaz.
  • Imprimamos también el nombre del tipo, thisya que está vinculado en la Spartaclase, solo para aclararlo todo.

El código se ve así:

public class Place {
    public interface Athena { }
}

public class Sparta : Place
{
    public void printTypeOfThis()
    {
        Console.WriteLine (this.GetType().Name);
    }

    public void printTypeOfSparta()
    {
        Console.WriteLine (typeof(Sparta));
    }

    public void printTypeOfAthena()
    {
        Console.WriteLine (typeof(Athena));
    }
}

Ahora creamos un Spartaobjeto y llamamos a los tres métodos.

public static void Main(string[] args)
    {
        Sparta s = new Sparta();
        s.printTypeOfThis();
        s.printTypeOfSparta();
        s.printTypeOfAthena();
    }
}

El resultado que obtenemos es:

Sparta
Athena
Place+Athena

Sin embargo, si modificamos la clase Place y definimos la interfaz Sparta:

   public class Place {
        public interface Athena { }
        public interface Sparta { } 
    }

entonces es esto Sparta, la interfaz, lo que estará disponible primero para el mecanismo de búsqueda de nombres y la salida de nuestro código cambiará a:

Sparta
Place+Sparta
Place+Athena

Por lo tanto, nos hemos equivocado con la comparación de tipos en la MakeItReturnFalsedefinición de la función simplemente definiendo la interfaz Sparta en la superclase, que se encuentra primero por la resolución de nombre.

Pero, ¿por qué C # eligió priorizar las interfaces definidas en la superclase en la resolución de nombres? @JonSkeet lo sabe! Y si lee su respuesta, obtendrá los detalles del protocolo de resolución de nombres en C #.

mircealungu
fuente