Como sabrá, las matrices en C # implementan IList<T>
, entre otras interfaces. Sin embargo, de alguna manera, hacen esto sin implementar públicamente la propiedad Count de IList<T>
! Las matrices solo tienen una propiedad Length.
¿Es este un ejemplo flagrante de C # /. NET que rompe sus propias reglas sobre la implementación de la interfaz o me falta algo?
Array
clase tenía que estar escrita en C #!Array
es una clase "mágica" que no se puede implementar en C # ni en ningún otro idioma orientado a .net. Pero esta característica específica está disponible en C #.Respuestas:
Nueva respuesta a la luz de la respuesta de Hans
Gracias a la respuesta de Hans, podemos ver que la implementación es algo más complicada de lo que pensamos. Tanto el compilador como el CLR se esfuerzan mucho en dar la impresión de que se implementa un tipo de matriz
IList<T>
, pero la variación de la matriz hace que esto sea más complicado. Contrariamente a la respuesta de Hans, los tipos de matriz (unidimensional, de base cero de todos modos) implementan las colecciones genéricas directamente, porque el tipo de cualquier matriz específica no lo esSystem.Array
, es solo el tipo base de la matriz. Si le pregunta a un tipo de matriz qué interfaces admite, incluye los tipos genéricos:Salida:
Para matrices unidimensionales basadas en cero, en lo que respecta al lenguaje , la matriz realmente
IList<T>
también se implementa . La sección 12.1.2 de la especificación de C # lo dice. Entonces, independientemente de lo que haga la implementación subyacente, el lenguaje tiene que comportarse como si fuera el tipo deT[]
implementosIList<T>
como con cualquier otra interfaz. Desde esta perspectiva, la interfaz se implementa con algunos de los miembros implementados explícitamente (comoCount
). Esa es la mejor explicación a nivel de idioma de lo que está sucediendo.Tenga en cuenta que esto solo es válido para matrices unidimensionales (y matrices de base cero, no que C # como lenguaje diga algo sobre las matrices de base no cero).
T[,]
no implementaIList<T>
.Desde la perspectiva de CLR, está sucediendo algo más divertido. No puede obtener la asignación de interfaz para los tipos de interfaz genéricos. Por ejemplo:
Da una excepción de:
Entonces, ¿por qué la rareza? Bueno, creo que realmente se debe a la covarianza de la matriz, que es una verruga en el sistema de tipos, en mi opinión. Aunque no
IList<T>
es covariante (y no puede ser seguro), la covarianza de matriz permite que esto funcione:... que lo hace parecer como
typeof(string[])
implementosIList<object>
, cuando no es así en realidad.La partición 1 de la especificación CLI (ECMA-335), sección 8.7.1, tiene esto:
...
(En realidad, no menciona
ICollection<W>
oIEnumerable<W>
creo que es un error en la especificación).Para la no variación, la especificación CLI va directamente junto con la especificación del idioma. De la sección 8.9.1 de la partición 1:
(Un vector es una matriz unidimensional con base cero).
Ahora bien, en cuanto a los detalles de implementación , claramente el CLR está haciendo un mapeo cobarde para mantener la compatibilidad de asignaciones aquí: cuando una
string[]
pregunta se hace por la ejecución deICollection<object>.Count
, que no puede manejar que, en todo el camino normal. ¿Esto cuenta como implementación de interfaz explícita? Creo que es razonable tratarlo de esa manera, ya que a menos que solicite el mapeo de la interfaz directamente, siempre se comportará de esa manera desde la perspectiva del lenguaje.¿Qué hay de
ICollection.Count
?Hasta ahora he hablado de las interfaces genéricas, pero luego están las no genéricas
ICollection
con suCount
propiedad. Esta vez podemos obtener la asignación de interfaz, y de hecho la interfaz se implementa directamenteSystem.Array
. La documentación para laICollection.Count
implementación de la propiedadArray
indica que se implementa con una implementación de interfaz explícita.Si alguien puede pensar en una forma en la que este tipo de implementación de interfaz explícita es diferente de la implementación de interfaz explícita "normal", estaría feliz de investigarlo más a fondo.
Respuesta anterior sobre la implementación de interfaz explícita
A pesar de lo anterior, que es más complicado debido al conocimiento de las matrices, aún puede hacer algo con los mismos efectos visibles a través de la implementación explícita de la interfaz .
Aquí hay un ejemplo sencillo e independiente:
fuente
Count
está bien, peroAdd
siempre arrojará, ya que las matrices son de tamaño fijo.Bueno, sí, eh, no, en realidad no. Esta es la declaración para la clase Array en el marco .NET 4:
Implementa System.Collections.IList, no System.Collections.Generic.IList <>. No puede, Array no es genérico. Lo mismo ocurre con las interfaces genéricas IEnumerable <> e ICollection <>.
Pero CLR crea tipos de matrices concretas sobre la marcha, por lo que técnicamente podría crear una que implemente estas interfaces. Sin embargo, este no es el caso. Pruebe este código, por ejemplo:
La llamada GetInterfaceMap () falla para un tipo de matriz concreto con "Interfaz no encontrada". Sin embargo, una conversión a IEnumerable <> funciona sin problemas.
Esta es la escritura de charlatanes como un pato. Es el mismo tipo de escritura que crea la ilusión de que cada tipo de valor deriva de ValueType que deriva de Object. Tanto el compilador como CLR tienen un conocimiento especial de los tipos de matriz, al igual que lo hacen de los tipos de valor. El compilador ve su intento de transmitir a IList <> y dice "¡está bien, sé cómo hacer eso!". Y emite la instrucción IL castclass. El CLR no tiene problemas con eso, sabe cómo proporcionar una implementación de IList <> que funcione en el objeto de matriz subyacente. Tiene conocimiento integrado de la clase System.SZArrayHelper, que de otro modo estaría oculta, un contenedor que realmente implementa estas interfaces.
Lo que no hace explícitamente como todos afirman, la propiedad Count sobre la que preguntó se ve así:
Sí, ciertamente puedes llamar a ese comentario "romper las reglas" :) Por lo demás, es muy útil. Y muy bien escondido, puede comprobarlo en SSCLI20, la distribución de fuente compartida para CLR. Busque "IList" para ver dónde tiene lugar la sustitución de tipo. El mejor lugar para verlo en acción es clr / src / vm / array.cpp, método GetActualImplementationForArrayGenericIListMethod ().
Este tipo de sustitución en CLR es bastante leve en comparación con lo que sucede en la proyección de lenguaje en CLR que permite escribir código administrado para WinRT (también conocido como Metro). Casi cualquier tipo de .NET principal se sustituye allí. IList <> se asigna a IVector <>, por ejemplo, un tipo completamente no administrado. En sí mismo una sustitución, COM no admite tipos genéricos.
Bueno, eso fue una mirada a lo que sucede detrás de la cortina. Pueden ser mares muy incómodos, extraños y desconocidos con dragones viviendo al final del mapa. Puede ser muy útil hacer que la Tierra sea plana y modelar una imagen diferente de lo que realmente está sucediendo en el código administrado. Asignarlo a la respuesta favorita de todos es cómodo de esa manera. Lo cual no funciona tan bien para los tipos de valor (¡no mutes una estructura!) Pero esta está muy bien oculta. La falla del método GetInterfaceMap () es la única fuga en la abstracción que se me ocurre.
fuente
Array
clase, que no es el tipo de una matriz. Es el tipo base de una matriz. Una matriz unidimensional en C # no implementarIList<T>
. Y un tipo no genérico ciertamente puede implementar una interfaz genérica de todos modos ... lo cual funciona porque hay muchos tipos diferentes -typeof(int[])
! = Typeof (string []), so
typeof (int []) `implementsIList<int>
, etypeof(string[])
implementsIList<string>
.Array
(que, como muestra, es una clase abstracta, por lo que posiblemente no puede ser el tipo real de un objeto de matriz) y la conclusión (que no implementaIList<T>
) son incorrectos en mi opinión. La forma en que se implementaIList<T>
es inusual e interesante, estoy de acuerdo, pero eso es puramente un detalle de implementación . Afirmar queT[]
no se implementaIList<T>
es engañoso en mi opinión. Va en contra de las especificaciones y de todo el comportamiento observado.IList<T>
porqueArray
no lo hacen? Esa lógica es una gran parte de lo que no estoy de acuerdo. Más allá de eso, creo que tendríamos que acordar una definición de lo que significa para un tipo implementar una interfaz: en mi opinión, los tipos de matriz muestran todas las características observables de los tipos que implementanIList<T>
, aparte deGetInterfaceMapping
. Una vez más, la forma en que se logra eso es de menor importancia para mí, al igual que estoy de acuerdo con decir queSystem.String
es inmutable, aunque los detalles de implementación son diferentes.IList<T>
que funcione.IList<T>.Count
se implementa explícitamente :Esto se hace para que cuando se tiene una variable de matriz simple, usted no tiene tanto
Count
yLength
directamente disponible.En general, la implementación de interfaz explícita se usa cuando desea asegurarse de que un tipo se pueda usar de una manera particular, sin obligar a todos los consumidores del tipo a pensarlo de esa manera.
Editar : Vaya, mal recuerdo allí.
ICollection.Count
se implementa explícitamente. El genéricoIList<T>
se maneja como Hans describe a continuación .fuente
string
).ICollection
declaraCount
, y sería aún más confuso si un tipo con la palabra "colección" no usaraCount
:). Siempre hay compensaciones al tomar estas decisiones.IList<T>
, a pesar de que tanto el lenguaje como las especificaciones CLI parecen lo contrario. Me atrevo a decir que la forma en que funciona la implementación de la interfaz bajo las cubiertas puede ser complicada, pero ese es el caso en muchas situaciones. ¿También rechazaría a alguien que diga queSystem.String
es inmutable, solo porque el funcionamiento interno es mutable? Para todos los propósitos prácticos, y ciertamente en lo que respecta al lenguaje C #, es implícito.Implementación de interfaz explícita . En resumen, lo declaras como
void IControl.Paint() { }
oint IList<T>.Count { get { return 0; } }
.fuente
No es diferente a una implementación de interfaz explícita de IList. El hecho de que implemente la interfaz no significa que sus miembros deban aparecer como miembros de la clase. Se hace aplicar la propiedad Count, simplemente no lo exponga en X [].
fuente
Con fuentes de referencia disponibles:
Específicamente esta parte:
(El énfasis es mío)
Fuente (desplazarse hacia arriba).
fuente