¿Por qué (realmente lo hace?) List implementa todas estas interfaces, no solo IList ?

Declaración de List de MSDN:

 public class List : IList, ICollection, IEnumerable, IList, ICollection, IEnumerable 

Reflector da una imagen similar. ¿Realmente implementa la List todos estos (si es así, por qué)? He comprobado:

  interface I1 {} interface I2 : I1 {} interface I3 : I2 {} class A : I3 {} class B : I3, I2, I1 {} static void Main(string[] args) { var a = new A(); var a1 = (I1)a; var a2 = (I2)a; var a3 = (I3)a; var b = new B(); var b1 = (I1) b; var b2 = (I2)b; var b3 = (I3)b; } 

comstack

[ACTUALIZADO]:

Chicos, según tengo entendido, todas las respuestas lo mantienen:

 class Program { interface I1 {} interface I2 : I1 {} interface I3 : I2 {} class A : I3 {} class B : I3, I2, I1 {} static void I1M(I1 i1) {} static void I2M(I2 i2) {} static void I3M(I3 i3) {} static void Main(string[] args) { var a = new A(); I1M(a); I2M(a); I3M(a); var b = new B(); I1M(b); I2M(b); I3M(b); Console.ReadLine(); } } 

daría error, pero se comstack y se ejecuta sin ningún error. ¿Por qué?

ACTUALIZACIÓN: Esta pregunta fue la base de mi entrada de blog para el lunes 4 de abril de 2011 . Gracias por la gran pregunta.

Déjame dividirlo en muchas preguntas más pequeñas.

¿ List realmente implementa todas esas interfaces?

Sí.

¿Por qué?

Porque cuando una interfaz (digamos, IList ) hereda de una interfaz (digamos IEnumerable ), los implementadores de la interfaz más derivada deben implementar también la interfaz menos derivada. Eso es lo que significa herencia de interfaz; si cumple el contrato del tipo más derivado, se le exige que también cumpla el contrato del tipo menos derivado.

Entonces, ¿se requiere una clase para implementar todos los métodos de todas las interfaces en el cierre transitivo de sus interfaces base?

Exactamente.

¿Una clase que implementa una interfaz más derivada también requiere indicar en su lista de tipos básicos que está implementando todas esas interfaces menos derivadas?

No.

¿Se requiere que la clase NO lo diga?

No.

Entonces, ¿es opcional si las interfaces implementadas menos derivadas se establecen en la lista de tipos base?

Sí.

¿Siempre?

Casi siempre:

 interface I1 {} interface I2 : I1 {} interface I3 : I2 {} 

Es opcional si I3 declara que hereda de I1.

 class B : I3 {} 

Los implementadores de I3 deben implementar I2 e I1, pero no están obligados a indicar explícitamente que lo están haciendo. Es opcional

 class D : B {} 

Las clases derivadas no requieren volver a indicar que implementan una interfaz desde su clase base, pero se les permite hacerlo. (Este caso es especial, ver más abajo para más detalles).

 class C where T : I3 { public virtual void M() where U : I3 {} } 

Los argumentos de tipo correspondientes a T y U son necesarios para implementar I2 e I1, pero es opcional para las restricciones en T o U decir eso.

Siempre es opcional restablecer cualquier interfaz base en una clase parcial:

 partial class E : I3 {} partial class E {} 

La segunda mitad de E tiene permiso para indicar que implementa I3 o I2 o I1, pero no es obligatorio.

Ok, lo entiendo; es opcional ¿Por qué alguien declararía innecesariamente una interfaz base?

Tal vez porque creen que hacerlo hace que el código sea más fácil de comprender y más autodocumentado.

O, tal vez el desarrollador escribió el código como

 interface I1 {} interface I2 {} interface I3 : I1, I2 {} 

y se dio cuenta, oh, espere un minuto, I2 debería heredar de I1. ¿Por qué hacer esa edición requiere que el desarrollador retroceda y cambie la statement de I3 para que no contenga una mención explícita de I1? No veo ninguna razón para obligar a los desarrolladores a eliminar la información redundante.

Además de ser más fácil de leer y comprender, ¿hay alguna diferencia técnica entre indicar una interfaz explícitamente en la lista de tipos básicos y dejarla no declarada pero implícita?

Por lo general, no, pero puede haber una diferencia sutil en un caso. Supongamos que tiene una clase derivada D cuya clase base B ha implementado algunas interfaces. D implementa automáticamente esas interfaces a través de B. Si vuelve a establecer las interfaces en la lista de clases base de D, el comstackdor de C # realizará una nueva implementación de la interfaz . Los detalles son un poco sutiles; Si le interesa cómo funciona esto, le recomiendo una lectura cuidadosa de la sección 13.4.6 de la especificación C # 4.

¿El código fuente de la List realidad indica todas esas interfaces?

No. El código fuente actual dice

 public class List : IList, System.Collections.IList 

¿Por qué MSDN tiene la lista de interfaz completa, pero el código fuente real no?

Porque MSDN es documentación; se supone que debe darle tanta información como desee. Es mucho más claro que la documentación esté completa en un solo lugar que hacer que busque entre diez páginas diferentes para descubrir cuál es el conjunto de interfaz completo.

¿Por qué Reflector muestra toda la lista?

Reflector solo tiene metadatos desde donde trabajar. Como poner en la lista completa es opcional, Reflector no tiene idea de si el código fuente original contiene la lista completa o no. Es mejor equivocarse al lado de más información. Una vez más, Reflector intenta ayudarlo mostrándole más información en lugar de ocultar la información que pueda necesitar.

PREGUNTA DE BONIFICACIÓN: ¿Por qué IEnumerable hereda de IEnumerable pero IList no hereda de IList ?

Una secuencia de enteros se puede tratar como una secuencia de objetos, encajonando cada entero a medida que sale de la secuencia. Pero una lista de lectura y escritura de enteros no puede tratarse como una lista de objetos de lectura y escritura, porque puede poner una cadena en una lista de objetos de lectura y escritura. Un IList no es necesario para cumplir con todo el contrato de IList , por lo que no hereda de él.

Las interfaces no genéricas son para compatibilidad hacia atrás. Si escribe código usando generics y desea pasar su lista a algún módulo escrito en .NET 1.0 (que no tiene generics), aún desea que esto tenga éxito. De ahí IList, ICollection, IEnumerable .

Gran pregunta y excelente respuesta de Eric Lippert. Esta pregunta vino a mi mente en el pasado, y entiendo lo siguiente, espero que te ayude.

Supongamos que soy un progtwigdor de C # en otro planeta en el universo, en este planeta, todos los animales pueden volar. Entonces mi progtwig se ve así:

 interface IFlyable { void Fly(); } interface IAnimal : IFlyable { //something } interface IBird : IAnimal, IFlyable { } 

Bueno, puede estar confundido, ya que las Birds son Animals y todos los Animals pueden volar, ¿por qué tenemos que especificar IFlyable en la interfaz IBird ? OK, vamos a cambiarlo a:

 interface IBird : IAnimal { } 

Estoy 100% seguro de que el progtwig funciona como antes, ¿por lo que nada ha cambiado? ¿El IFlyable en la interfaz IBird es totalmente inútil? Continúemos.

Un día, mi compañía vendió el software a otra compañía en la Tierra. Pero en la Tierra, ¡no todos los animales pueden volar! Por supuesto, necesitamos modificar la interfaz IAnimal y las clases que la implementan. ¡Después de la modificación, encontré que IBird ahora es incorrecto porque los pájaros en la tierra pueden volar! ¡Ahora me arrepiento de haber eliminado IFlyable de IBird !

  1. Sí, lo hacen, porque List puede tener las propiedades y el método para cumplir con todas esas interfaces. No sabe qué interfaz va a haber declarado alguien como parámetro o valor de retorno, por lo que cuanto más listas implemente la interfaz, más versátil puede ser.
  2. Su código se comstackrá porque el upcasting ( var a1 = (I1)a; ) falla en tiempo de ejecución, no en tiempo de comstackción. Puedo hacer var a1 = (int)a y hacer que compile.

List<> implementa todas esas interfaces para exponer la funcionalidad descrita por las diferentes interfaces. Comprende las características de una Lista genérica, Colección y Enumerable (con compatibilidad hacia atrás con los equivalentes no generics)