De hecho, es por motivos de rendimiento. El equipo de BCL hizo un lote de investigación sobre este punto antes de decidirse por lo que con razón califica como una práctica sospechosa y peligrosa: el uso de un tipo de valor variable.
Usted pregunta por qué esto no causa el boxeo. ¡Es porque el compilador de C# no genera código para encapsular cosas en IEnumerable o IEnumerator en un bucle foreach si puede evitarlo!
Cuando vemos
foreach(X x in c)
el primero que hacemos es comprobar para ver si c tiene un método llamado GetEnumerator. Si lo hace, entonces verificamos si el tipo que devuelve tiene el método MoveNext y la propiedad actual. Si lo hace, entonces el bucle foreach se genera completamente usando llamadas directas a esos métodos y propiedades. Solo si "el patrón" no puede coincidir, volvemos a buscar las interfaces.
Esto tiene dos efectos deseables.
Primero, si la colección es, por ejemplo, una colección de ints, pero se escribió antes de que se inventaran los tipos genéricos, no se aplica la penalización de boxeo del valor actual para objetar y luego desempaquetarlo en int. Si Current es una propiedad que devuelve un int, solo la usamos.
En segundo lugar, si el enumerador es un tipo de valor, no coloca el enumerador en el Enumerador.
Como he dicho, el equipo BCL hicieron un gran trabajo de investigación sobre este y descubrieron que la gran mayoría de las veces, la pena de asignar y desasignar el empadronador era lo suficientemente grande que valía la pena por lo que es un tipo de valor , aunque hacerlo puede causar algunos errores locos.
Por ejemplo, considere esto:
struct MyHandle : IDisposable { ... }
...
using (MyHandle h = whatever)
{
h = somethingElse;
}
Se podría esperar que con toda razón el intento de mutar h a fallar, y de hecho lo hace. El compilador detecta que está tratando de cambiar el valor de algo que tiene una disposición pendiente, y que hacerlo podría causar que el objeto que necesita ser eliminado no se elimine.
Ahora supongamos que tiene:
struct MyHandle : IDisposable { ... }
...
using (MyHandle h = whatever)
{
h.Mutate();
}
lo que sucede aquí? Es razonable esperar que el compilador haga lo que hace si h fuera un campo de solo lectura: make a copy, and mutate the copy para garantizar que el método no elimine las cosas en el valor que debe eliminarse.
Sin embargo, esto entra en conflicto con nuestra intuición acerca de lo que debe suceder aquí:
using (Enumerator enumtor = whatever)
{
...
enumtor.MoveNext();
...
}
Esperamos que haciendo un MoveNext dentro de un bloque using se mover el empadronador a la siguiente, independientemente de si se trata de una estructura o un tipo de ref.
Desafortunadamente, el compilador de C# hoy tiene un error. Si se encuentra en esta situación, elegimos qué estrategia seguir de forma inconsistente. El comportamiento de hoy es:
si la variable de valor tecleado mutado a través de un método es un local de lo normal, entonces se muta normalmente
pero si se trata de un local de izado (porque es un cerrado sobre la variable de una función anónima o en un bloque iterador) entonces el local es realmente generado como un campo de solo lectura, y el engranaje que asegura que las mutaciones suceden en una copia toma el control.
Desafortunadamente, la especificación proporciona poca orientación sobre este asunto. Claramente, algo se rompe porque lo estamos haciendo de manera inconsistente, pero lo que la derecha hacer no está nada clara.
páginas relacionadas para los demás corren a través de esto: http://stackoverflow.com/questions/384511/enumerator-implementation-use-struct-or-class http://www.eggheadcafe.com/software/ aspnet/31702392/c-compiler-challenge - s.aspx –