2008-11-05 27 views
29

Por ejemplo:¿Por qué una clase no puede extender su propia clase anidada en C#?

public class A : A.B 
{ 
    public class B { } 
} 

que genera este error del compilador:

base circular dependencia de la clase que implica 'A' y 'AB'

siempre me imaginé un anidado La clase se comportó como una clase normal, excepto con reglas especiales sobre el acceso a los miembros privados de la clase externa, pero supongo que hay una herencia implícita entre los dos classe. s?

+2

Me pregunto, ¿hay alguna razón en particular por la que le gustaría hacer esto, o la publicó para el debate y el aprendizaje? ¿Cuál sería la aplicación práctica si fuera posible? – Daan

+0

@Daan durante la implementación de una interfaz fluida de patrón genérico de compilador, tengo interfaces en una clase genérica que deseo implementar en esa misma clase. debido a este problema, tengo que mover las interfaces a una clase separada (tiene que estar en una clase para que puedan compartir los tipos genéricos y las restricciones). esto ha hecho que la implementación explícita de la interfaz sea extremadamente fea, ya que tengo que referirme a esa otra clase. (En realidad evité el error y la fealdad al heredar de esa otra clase ...) –

Respuesta

32

No hay herencia implícita implicada por lo que puedo decir. Hubiera esperado que todo estuviera bien, aunque puedo imaginar rareza si A y B son genéricos.

Se especifica en la sección 10.1.4 de la especificación:

Cuando una clase B se deriva de una clase A, se trata de un error de tiempo de compilación para la A a la dependen de B. Una clase directamente depende en su clase de base directa (si existe) y depende directamente de la clase dentro de que se anida inmediatamente (si alguno). Dada esta definición, el conjunto completo de clases del que depende una clase es el cierre transitivo de la relación directa .

He resaltado la sección correspondiente.

Eso explica por qué el compilador lo está rechazando, pero no por qué el lenguaje lo prohíbe. Me pregunto si hay una restricción CLI ...

EDIT: Bien, he recibido una respuesta de Eric Lippert. Básicamente, sería técnicamente posible (no hay nada en la CLI para prohibirla), pero:

  • lo que le sería difícil en el compilador, invalidando diversas hipótesis actuales sobre la ordenación y ciclos
  • Es bastante extraño decisión de diseño que es más fácil para prohibir que apoyar

también se observó en el hilo de correo electrónico que haría este tipo de cosas válida:

A.B x = new A.B.B.B.B.B.B.B.B.B.B.B.B(); 

... pero eso ya sería (según lo observado por Tinister) será válida si B deriva de A.

la jerarquización + herencia = excentricidad ...

+0

Estoy confundido sobre cómo sería eso válido. A extiende B y B no tiene una clase anidada A, o algo ... – Tinister

+0

Además, si B extendió A, este tipo de cosas ya es posible: A.B.B.B.B.B.B.B.B.B.B.B.B.B.Foo(); – Tinister

+0

Tinister: Tienes razón, tengo el ejemplo equivocado :) Se editará. –

-2

Esto no tiene sentido para mí ... Eres tratando de extender algo que no existe !!! La clase B solo existe en el ámbito de la clase A y, debido a esto, creo que hay algún tipo de herencia.

+1

No, no hay herencia implícita. Es perfectamente posible referirse a A.B desde fuera de A. –

+0

OK, la herencia no era la palabra correcta ... tal vez la dependencia. –

+1

Pero, ¿dónde exactamente está la dependencia? ¿Qué * exactamente * iría mal si se permitiera el código anterior? –

0

Creo que el anidamiento representa que el tipo anidado es parte de la definición del tipo de anidación.Con esa interpretación, la limitación tiene sentido porque en el momento en que el compilador alcanza la definición de A, A.B aún no está definido, e incluso al final de A, ya está definido en términos de A.B.

+2

¿Por qué es esto diferente del tipo A que tiene un campo de tipo B, y el tipo B tiene un campo de tipo A? El compilador ya tiene que lidiar con ese tipo de situación ... sospecho que puede ser por razones de cordura humana en lugar de razones técnicas reales. –

13

Esto no es tanto una cosa de C# como algo del compilador. Uno de los trabajos de un compilador es diseñar una clase en la memoria, es decir, un conjunto de tipos de datos básicos, punteros, punteros a funciones y otras clases.

No puede construir el diseño para la clase A hasta que sepa cuál es el diseño de la clase B. No puede saber cuál es el diseño de la clase B hasta que finaliza con el diseño de la clase A. Dependencia circular.

+4

No estoy seguro de lo que quieres decir con esto. ¿Por qué no podría hacer primero la clase anidada? Eso en realidad no tiene ninguna dependencia en la clase externa en términos de diseño, ¿o sí? –

0

cuanto a las preguntas acerca de lo que yo estaba tratando de hacer:

Básicamente, quería crear una clase que tenía una relación con la composición en sí, pero yo no quiero tener el objeto contenido para contener otros objetos y por lo tanto, cree una cadena con muchas relaciones "A tiene-un A tiene-un A tiene-un A tiene-un ...". Así que mi pensamiento en ese momento era hacer algo como esto:

public class A : A.AA 
{ 
    public class AA 
    { 
     // All of the class's logic 
    } 

    private AA _containedObject; 
} 

que en el momento parecía bastante resbaladiza, pero en retrospectiva, no estoy tan seguro ...

había hurgado través de Google y didn' No encuentro ninguna buena discusión, así que pensé en publicarlo aquí.

Sin embargo, en los comentarios de post at Eric Lippert's Blog da ejemplos de una clase implementando una interfaz anidada, así como una clase que implementa una interfaz genérica con una clase anidada como el argumento tipo (que no compila y llama a un " error "en el compilador actual). Ambos ejemplos se refieren a interfaces, por lo que me preguntaba si había algunas reglas especiales con clases anidadas. Y parece que hay

+0

Ciertamente existen reglas especiales, según la especificación. Pero es fascinante preguntar * por qué * esas reglas existen :) –

0

Pude evitar esto (al menos con interfaces) al heredar de una clase separada que contiene las interfaces anidadas. (En mi escenario también estoy volviendo referencias a estas interfaces.)

En lugar de:

public class MyClass<T1, T2, T3> : 
    MyClass<T1, T2, T3>.Interface 
where T1 : ... 
where T2 : ... 
where T3 : ... { 
    public interface Interface { Interface SomeMethod(); } 

    Interface Interface.SomeMethod() { 
     ... 
    } 
} 

// compile error: Circular base class dependency 

hacer algo como esto:

public sealed class MyClassInterfaces<T1, T2, T3> 
where T1 : ... 
where T2 : ... 
where T3 : ... { 
    public interface Interface { Interface SomeMethod(); } 
} 

sealed class MyClass<T1, T2, T3> : 
    MyClassInterfaces<T1, T2, T3>.Interface 
where T1 : ... 
where T2 : ... 
where T3 : ... { 
    MyClassInterfaces<T1, T2, T3>.Interface 
    MyClassInterfaces<T1, T2, T3>.Interface.SomeMethod() { 
     ... 
    } 
} 

Para evitar la fealdad con implementaciones de interfaces explícitas, se también puede heredar de la otra clase, aunque eso no funcionaría si estuviera tratando de heredar de una clase anidada, ya que no puede heredar de ambas clases.

public abstract class MyClassInterfaces<T1, T2, T3> 
where T1 : ... 
where T2 : ... 
where T3 : ... { 
    public interface Interface { Interface SomeMethod(); } 
} 

sealed class MyClass<T1, T2, T3> : 
    MyClassInterfaces<T1, T2, T3>, 
    MyClassInterfaces<T1, T2, T3>.Interface 
where T1 : ... 
where T2 : ... 
where T3 : ... { 
    Interface Interface.SomeMethod() { 
     ... 
    } 
} 
Cuestiones relacionadas