c# - explained - ¿Por qué IEnumerable<T> hereda de IEnumerable?
read ienumerable c# (4)
Es por compatibilidad con versiones anteriores. Si llama a una función .Net 1.1 que espera un IEnumerable vainilla, puede pasar su IEnumerable genérico.
Por suerte, el genérico IEnumerator hereda del antiguo IEnumerator
Por lo general, implemento un método privado que devuelve un enumerador y luego lo paso tanto para el método GetEnumerator antiguo como para el nuevo.
private IEnumerator<string> Enumerator() {
// ...
}
public IEnumerator<string> GetEnumerator() {
return Enumerator();
}
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() {
return Enumerator();
}
Esta podría ser una vieja pregunta: ¿por qué IEnumerable<T>
hereda de IEnumerable
?
Así es como lo hace .NET, pero trae un pequeño problema. Cada vez que escribo una clase implementa IEumerable<T>
, tengo que escribir dos funciones GetEnumerator()
, una para IEnumerable<T>
y otra para IEnumerable
.
Y, IList<T>
no hereda de IList.
No sé por qué IEnumerable<T>
está diseñado de otra manera.
Esto es para que funcione con clases que no admiten genéricos. Además, los genéricos .NET no le permiten hacer cosas como lanzar IList <long> como IList <int>, de modo que las versiones no genéricas de las interfaces pueden ser bastante útiles cuando necesita una clase base o interfaz fija.
La respuesta para IEnumerable
es: "porque puede afectar la seguridad del tipo".
IEnumerable
es una interfaz de "solo lectura", por lo que no importa que la forma genérica sea más específica que la no genérica. No se rompe nada implementando ambos. IEnumerator.Current
devuelve el object
, mientras que IEnumerator<T>.Current
devuelve T
, está bien, ya que siempre puedes convertirlo legítimamente a object
, aunque puede significar boxeo.
Compare esto con IList<T>
e IList
: puede llamar a Add(object)
en un IList
, mientras que eso puede ser inválido para cualquier IList<T>
(cualquier cosa que no sea IList<object>
de hecho).
Brad Abram escribió un blog con la respuesta de Anders sobre esta misma pregunta.
Directamente de la boca del caballo (Hejlsberg):
Idealmente, todas las interfaces genéricas de recopilación (por ejemplo,
ICollection<T>
,IList<T>
) heredarían de sus contrapartes no genéricas, de manera que las instancias de interfaz genéricas se podrían usar tanto con código genérico como no genérico. Por ejemplo, sería conveniente si se pudiera pasar unIList<T>
al código que espera unIList
.Como resultado, la única interfaz genérica para la que esto es posible es
IEnumerable<T>
, porque soloIEnumerable<T>
esIEnumerable<T>
: enIEnumerable<T>
, el parámetro de tipo T se usa solo en posiciones de "salida" ( valores de retorno) y no en posiciones de "entrada" (parámetros).ICollection<T>
eIList<T>
usan T en ambas posiciones de entrada y salida, y esas interfaces son por lo tanto invariantes. (Por otro lado, habrían sido contravariantes si T se usara solo en posiciones de entrada, pero eso realmente no importa aquí).<... snip ...>
Entonces, para responder a su pregunta, IEnumerable<T>
hereda de IEnumerable
porque puede! :-)