ventajas - Pato escribiendo en el compilador C#
ventajas de c# (3)
Nota: Esta no es una pregunta acerca de cómo implementar o emular la tipificación de pato en C # ...
Durante varios años tuve la impresión de que ciertas características del lenguaje C # eran depdendent en las estructuras de datos definidas en el idioma en sí (que siempre me pareció un extraño escenario de pollo y huevo). Por ejemplo, tenía la impresión de que el ciclo foreach
solo estaba disponible para usar con los tipos que implementaban IEnumerable
.
Desde entonces, he llegado a comprender que el compilador de C # usa el tipado de pato para determinar si un objeto se puede usar en un ciclo foreach, buscando un método GetEnumerator
lugar de IEnumerable
. Esto tiene mucho sentido ya que elimina el acertijo del pollo y el huevo.
Estoy un poco confundido sobre por qué no es así, no parece ser el caso con el using
bloque y IDisposable
. ¿Hay alguna razón en particular por la que el compilador no pueda usar la tipificación de pato y busque un método de Dispose
? ¿Cuál es el motivo de esta incoherencia?
Tal vez hay algo más pasando bajo el capó con IDisposable?
Discutir por qué alguna vez tendrías un objeto con un método Dispose que no implementó IDisposable está fuera del alcance de esta pregunta :)
No hay nada especial sobre IDisposable
aquí, pero hay algo especial sobre los iteradores.
Antes de C # 2, usar este tipo de pato en foreach
era lo único que se podía implementar con un iterador fuertemente tipado, y también era la única forma de iterar sobre tipos de valores sin boxeo. Sospecho que si C # y .NET tuvieran genéricos para empezar, foreach
habría requerido IEnumerable<T>
lugar, y no tenía el pato escribiendo.
Ahora el compilador utiliza este tipo de pato escribiendo en un par de otros lugares en los que puedo pensar:
- Los inicializadores de colecciones buscan una sobrecarga de
Add
adecuada (así como el tipo que tiene que implementarIEnumerable
, solo para mostrar que realmente es una colección de algún tipo); esto permite la adición flexible de elementos individuales, pares clave / valor, etc. - LINQ (
Select
etc.): así es como LINQ logra su flexibilidad, permitiendo el mismo formato de expresión de consulta contra múltiples tipos, sin tener que cambiarIEnumerable<T>
sí mismo - Las expresiones de espera de C # 5 requieren que
GetAwaiter
devuelva un tipo de awaiter que tieneIsCompleted
/OnCompleted
/GetResult
En ambos casos, esto hace que sea más fácil agregar la característica a los tipos e interfaces existentes, donde el concepto no existía anteriormente.
Dado que IDisposable
ha estado en el marco desde la primera versión, no creo que haya ningún beneficio en pato al escribir la sentencia using
. Sé que trataste explícitamente de descartar las razones para disponer de Dispose
sin implementar IDisposable
de la discusión, pero creo que es un punto crucial. Debe haber buenas razones para implementar una característica en el lenguaje, y yo diría que el tipado de pato es una característica que va más allá de respaldar una interfaz conocida. Si no hay un beneficio claro al hacerlo, no terminará en el idioma.
No hay pollo ni huevo: foreach
podría depender de IEnumerable
ya que IEnumerable
no depende de foreach
. La razón por la cual se permite foreach en las colecciones que no se implementan. IEnumerable
es probablemente en gran parte histórico :
En C #, no es estrictamente necesario que una clase de colección herede de IEnumerable e IEnumerator para ser compatible con foreach; siempre que la clase tenga los elementos requeridos GetEnumerator, MoveNext, Reset y Current, funcionará con foreach. Omitir las interfaces tiene la ventaja de permitirle definir el tipo de retorno de la Corriente para que sea más específico que el objeto, proporcionando así seguridad de tipo.
Además, no todos los problemas de huevo y gallina son realmente problemas: por ejemplo, una función puede llamarse a sí misma (¡recursividad!) O un tipo de referencia puede contenerse (como una lista vinculada).
Entonces, cuando se using
, ¿por qué iban a usar algo tan complicado de especificar como pato escribiendo cuando simplemente pueden decir: implementar IDisposable
? Fundamentalmente, al usar el tipado de pato, se está ejecutando un ciclo final alrededor del sistema de tipo, que solo es útil cuando el sistema de tipo es insuficiente (o poco práctico) para abordar un problema.
La pregunta que estás preguntando no es una situación de gallina y huevo. Es más como cómo se implementa el compilador de lenguaje. Como el compilador C # y VB.NET se implementan de forma diferente. Si escribe un código simple de hello world y lo compila con el compilador e inspecciona el código IL, será diferente. Volviendo a su pregunta, me gustaría explicar qué código de IL genera el compilador de C # para IEnumerable
.
IEnumerator e = arr.GetEnumerator();
while(e.MoveNext())
{
e.Currrent;
}
Entonces, el compilador C # se ajusta para el caso de foreach
.