¿Cómo las matrices en C # implementan parcialmente IList ?

Entonces, como sabrá, las matrices en C # implementan IList , entre otras interfaces. De alguna manera, sin embargo, lo hacen sin implementar públicamente la propiedad Count de IList ! Las matrices solo tienen una propiedad de longitud.

¿Es este un claro ejemplo de que C # /. NET está rompiendo sus propias reglas sobre la implementación de la interfaz o me falta algo?

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 podríamos pensar. Tanto el comstackdor como el CLR intentan dar la impresión de que un tipo de matriz implementa IList , pero la varianza de la matriz hace que esto sea más complicado. Contrariamente a la respuesta de Hans, los tipos de matriz (unidimensionales, basados ​​en cero de todos modos) implementan las colecciones genéricas directamente, porque el tipo de una matriz específica no es System.Array : ese es solo el tipo base de la matriz. Si le preguntas a una matriz, qué interfaces admite, incluye los tipos generics:

 foreach (var type in typeof(int[]).GetInterfaces()) { Console.WriteLine(type); } 

Salida:

 System.ICloneable System.Collections.IList System.Collections.ICollection System.Collections.IEnumerable System.Collections.IStructuralComparable System.Collections.IStructuralEquatable System.Collections.Generic.IList`1[System.Int32] System.Collections.Generic.ICollection`1[System.Int32] System.Collections.Generic.IEnumerable`1[System.Int32] 

Para arreglos unidimensionales basados ​​en cero, en lo que respecta al lenguaje , la matriz realmente implementa IList también. La sección 12.1.2 de la especificación C # lo dice. Entonces, cualquiera que sea la implementación subyacente, el lenguaje debe comportarse como si el tipo de T[] implementara IList como con cualquier otra interfaz. Desde esta perspectiva, la interfaz se implementa con algunos de los miembros que se implementan explícitamente (como Count ). Esa es la mejor explicación a nivel de lenguaje para lo que está sucediendo.

Tenga en cuenta que esto solo se aplica a las matrices unidimensionales (y las matrices basadas en cero, no a que C # como un lenguaje dice algo acerca de las matrices no basadas en cero). T[,] no implementa IList .

Desde una perspectiva CLR, algo más divertido está sucediendo. No puede obtener la asignación de interfaz para los tipos de interfaz generics. Por ejemplo:

 typeof(int[]).GetInterfaceMap(typeof(ICollection)) 

Da una excepción de:

 Unhandled Exception: System.ArgumentException: Interface maps for generic interfaces on arrays cannot be retrived. 

Entonces, ¿por qué la rareza? Bueno, creo que realmente se debe a la matriz de covarianza, que es una verruga en el sistema de tipos, IMO. Aunque IList no es covariante (y no puede ser segura), la covarianza de matriz permite que esto funcione:

 string[] strings = { "a", "b", "c" }; IList objects = strings; 

… lo que hace que se vea como typeof(string[]) implementa IList , cuando realmente no lo hace.

La partición 1 de la CLI spec (ECMA-335), sección 8.7.1, tiene esto:

Un tipo de firma T es compatible, con un tipo de firma U, si y solo si al menos una de las siguientes retenciones

T es una matriz de rango 1 basada en cero V[] , y U es IList , y V es compatible con elementos de matriz-con W.

(En realidad, no menciona ICollection o IEnumerable que creo que es un error en la especificación).

Para la no varianza, la especificación CLI va junto con la especificación del idioma directamente. De la sección 8.9.1 de la partición 1:

Además, un vector creado con elemento tipo T, implementa la interfaz System.Collections.Generic.IList , donde U: = T. (§8.7)

(Un vector es una matriz unidimensional con una base cero).

Ahora, en términos de los detalles de implementación , claramente el CLR está haciendo un mapeo funky para mantener la compatibilidad de las asignaciones aquí: cuando se solicita una string[] para la implementación de ICollection.Count , no puede manejar eso en bastante manera normal ¿Esto cuenta como implementación de interfaz explícita? Creo que es razonable tratarlo de esa manera, ya que a menos que solicite la asignación de interfaz directamente, siempre se comporta de esa manera desde una perspectiva de lenguaje.

¿Qué pasa con ICollection.Count ?

Hasta ahora he hablado sobre las interfaces genéricas, pero luego está la ICollection no genérica con su propiedad Count . Esta vez podemos obtener la asignación de interfaz, y de hecho, la interfaz se implementa directamente mediante System.Array . La documentación para la implementación de la propiedad ICollection.Count en Array establece que se implementa con la implementación de interfaz explícita.

Si alguien puede pensar en una forma en la que este tipo de implementación explícita de la interfaz es diferente de la implementación de la interfaz explícita “normal”, estaría encantado de investigar más a fondo.

Respuesta anterior a 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 simple e independiente:

 public interface IFoo { void M1(); void M2(); } public class Foo : IFoo { // Explicit interface implementation void IFoo.M1() {} // Implicit interface implementation public void M2() {} } class Test { static void Main() { Foo foo = new Foo(); foo.M1(); // Compile-time failure foo.M2(); // Fine IFoo ifoo = foo; ifoo.M1(); // Fine ifoo.M2(); // Fine } } 

Entonces, como sabrá, las matrices en C # implementan IList , entre otras interfaces

Bueno, sí, erm no, en realidad no. Esta es la statement para la clase Array en el marco .NET 4:

 [Serializable, ComVisible(true)] public abstract class Array : ICloneable, IList, ICollection, IEnumerable, IStructuralComparable, IStructuralEquatable { // etc.. } 

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 el CLR crea tipos de matriz concretos sobre la marcha, por lo que técnicamente podría crear uno que implemente estas interfaces. Sin embargo, este no es el caso. Pruebe este código, por ejemplo:

 using System; using System.Collections.Generic; class Program { static void Main(string[] args) { var goodmap = typeof(Derived).GetInterfaceMap(typeof(IEnumerable)); var badmap = typeof(int[]).GetInterfaceMap(typeof(IEnumerable)); // Kaboom } } abstract class Base { } class Derived : Base, IEnumerable { public IEnumerator GetEnumerator() { return null; } System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { return GetEnumerator(); } } 

La llamada GetInterfaceMap () falla para un tipo concreto de matriz con “Interface not found”. Sin embargo, un elenco para IEnumerable <> funciona sin problemas.

Esta es la tipificación de cuaques como patos. Es el mismo tipo de tipeo que crea la ilusión de que cada tipo de valor se deriva de ValueType que se deriva de Object. Tanto el comstackdor como el CLR tienen un conocimiento especial de los tipos de matriz, al igual que lo hacen con los tipos de valores. El comstackdor ve tu bash de conversión a IList <> y dice “está bien, ¡sé cómo hacer eso!”. Y emite la instrucción IL de castclass. El CLR no tiene problemas, sabe cómo proporcionar una implementación de IList <> que funcione en el objeto de matriz subyacente. Tiene conocimiento incorporado de la clase System.SZArrayHelper que de otra manera estaría oculta, un contenedor que realmente implementa estas interfaces.

Lo que no hace explícitamente, como todo el mundo dice, la propiedad de Count sobre la que preguntaste se ve así:

  internal int get_Count() { //! Warning: "this" is an array, not an SZArrayHelper. See comments above //! or you may introduce a security hole! T[] _this = JitHelpers.UnsafeCast(this); return _this.Length; } 

Sí, ciertamente puede llamar a ese comentario “rompiendo las reglas” 🙂 De lo contrario, es muy útil. Y muy bien escondido, puedes ver esto en SSCLI20, la distribución de fuente compartida para el CLR. Busque “IList” para ver dónde se produce la sustitución de tipo. El mejor lugar para verlo en acción es el método clr / src / vm / array.cpp, GetActualImplementationForArrayGenericIListMethod ().

Este tipo de sustitución en el CLR es bastante leve en comparación con lo que sucede en la proyección del lenguaje en el CLR que permite escribir código administrado para WinRT (también conocido como Metro). Casi cualquier tipo de núcleo .NET se sustituye allí. IList <> se asigna a IVector <> por ejemplo, un tipo completamente no administrado. En sí mismo una sustitución, COM no es compatible con los tipos generics.

Bueno, eso fue una mirada a lo que sucede detrás de la cortina. Puede ser muy incómodo, mares extraños y desconocidos con dragones que viven al final del mapa. Puede ser muy útil hacer plana la Tierra 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 este está muy bien escondido. El error del método GetInterfaceMap () es la única fuga en la abstracción que se me ocurre.

IList.Count se implementa de forma explícita :

 int[] intArray = new int[10]; IList intArrayAsList = (IList)intArray; Debug.Assert(intArrayAsList.Count == 10); 

Esto se hace para que cuando tenga una variable de matriz simple, no tenga el Count y la Length disponibles directamente.

En general, la implementación de interfaz explícita se utiliza cuando desea asegurarse de que un tipo se puede usar de una manera particular, sin forzar a todos los consumidores del tipo a pensar de esa manera.

Editar : Vaya, mal recuerdo allí. ICollection.Count se implementa de forma explícita. El IList genérico IList se maneja como Hans lo describe a continuación .

Implementación de interfaz explícita . En resumen, lo declaras como void IControl.Paint() { } o int IList.Count { get { return 0; } } int IList.Count { get { return 0; } } .

No es diferente de una implementación de interfaz explícita de IList. El hecho de que implemente la interfaz no significa que sus miembros necesiten aparecer como miembros de la clase. Implementa la propiedad Count, simplemente no la expone en X [].

Con fonts de referencia disponibles:

 //---------------------------------------------------------------------------------------- // ! READ THIS BEFORE YOU WORK ON THIS CLASS. // // The methods on this class must be written VERY carefully to avoid introducing security holes. // That's because they are invoked with special "this"! The "this" object // for all of these methods are not SZArrayHelper objects. Rather, they are of type U[] // where U[] is castable to T[]. No actual SZArrayHelper object is ever instantiated. Thus, you will // see a lot of expressions that cast "this" "T[]". // // This class is needed to allow an SZ array of type T[] to expose IList, // IList, etc., etc. all the way up to IList. When the following call is // made: // // ((IList) (new U[n])).SomeIListMethod() // // the interface stub dispatcher treats this as a special case, loads up SZArrayHelper, // finds the corresponding generic method (matched simply by method name), instantiates // it for type  and executes it. // // The "T" will reflect the interface used to invoke the method. The actual runtime "this" will be // array that is castable to "T[]" (ie for primitivs and valuetypes, it will be exactly // "T[]" - for orefs, it may be a "U[]" where U derives from T.) //---------------------------------------------------------------------------------------- sealed class SZArrayHelper { // It is never legal to instantiate this class. private SZArrayHelper() { Contract.Assert(false, "Hey! How'd I get here?"); } /* ... snip ... */ } 

Específicamente esta parte:

el asignador de interfaz stub trata esto como un caso especial , carga SZArrayHelper, encuentra el método genérico correspondiente (emparejado simplemente por el nombre del método) , crea una instancia para el tipo y lo ejecuta.

(Énfasis mío)

Fuente (desplazamiento hacia arriba).