Pregunta

mutables son estructuras el mal en general. También estoy bastante seguro de que debido a que el tipo vuelve IEnumerable<T>.GetEnumerator() IEnumerator<T>, las estructuras están en caja inmediatamente en un tipo de referencia, que cuestan más que si fueran simplemente los tipos de referencia, para empezar.

Así que por qué, en las colecciones genéricas BCL, son todos los enumeradores estructuras mutables? Seguramente tenía que haber sido una buena razón. Lo único que se me ocurre es que estructuras se pueden copiar fácilmente, preservando así el estado enumerador en un punto arbitrario. Sin embargo, la adición de un método Copy() a la interfaz IEnumerator habría sido menos problemático, por lo que no veo esto como una justificación lógica por sí sola.

Incluso si no estoy de acuerdo con una decisión de diseño, me gustaría ser capaz de entender el razonamiento detrás de él.

¿Fue útil?

Solución

De hecho, es por razones de rendimiento. El equipo BCL hizo un mucho de la investigación sobre este punto antes de decidirse a ir con lo que se llama con razón como una sospechosa y peligrosa práctica:. El uso de un tipo de valor mutable

Usted pregunta por qué esto no causa el boxeo. Es debido a que el compilador de C # no genera código para caja de pulpa a IEnumerable o IEnumerator en un bucle foreach si puede evitarlo!

Cuando vemos

foreach(X x in c)

lo primero que hacemos es comprobar para ver si c tiene un método llamado GetEnumerator. Si lo hace, entonces, comprobamos para ver si el tipo que devuelve tiene método MoveNext y actual propiedad. Si lo hace, entonces el bucle foreach se genera utilizando enteramente llamadas directas a los métodos y propiedades. Sólo si "el patrón" no puede ser igualada hacer volvemos a caer a la búsqueda de las interfaces.

Esto tiene dos efectos deseables.

En primer lugar, si la colección es, por ejemplo, una colección de enteros, pero fue escrito antes de que se inventaran los tipos genéricos, entonces no se necesita la pena de boxeo de boxeo el valor actual del objeto y luego a unboxing a int. Si la corriente es una propiedad que devuelve un int, sólo lo utilizan.

En segundo lugar, si el empadronador es un tipo de valor a continuación, sólo ingriesa el empadronador a IEnumerator.

Como dije, el equipo BCL hizo un gran trabajo de investigación sobre este y descubrió que la gran mayoría de las veces, la pena de asignar y desasignar el empadronador fue lo suficientemente grande que valía la pena hacer es un tipo de valor, a pesar de que al hacerlo puede causar algunos errores locos.

Por ejemplo, considere esto:

struct MyHandle : IDisposable { ... }
...
using (MyHandle h = whatever)
{
    h = somethingElse;
}

que se puede esperar con toda razón el intento de mutar h a fallar, y de hecho lo hace. Los detecta compilador que está tratando de cambiar el valor de algo que tiene una disposición en espera, y que al hacerlo podría hacer que el objeto que debe eliminarse en realidad no se pueden eliminar.

Ahora supongamos que tiene:

struct MyHandle : IDisposable { ... }
...
using (MyHandle h = whatever)
{
    h.Mutate();
}

¿Qué pasa aquí? Se podría esperar razonablemente que el compilador haga lo que hace si h eran un campo de sólo lectura: hacer una copia, y mutar la copia con el fin de asegurar que el método no tirar cosas en el valor que tienen que ser eliminados.

Sin embargo, que 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 usando mover el empadronador a la siguiente, independientemente de si se trata de una estructura o un tipo ref.

Por desgracia, el compilador de C # hoy tiene un error. Si se encuentra en esta situación elegimos la estrategia a seguir de manera inconsistente. El comportamiento de hoy es:

  • si la variable de valor tecleado ser mutado a través de un método es una normal local entonces se muta normalmente

  • pero si se trata de un local de izado (porque es una variable cerrada en off de una función anónima o en un bloque de iterador), entonces el local de es realmente se genera como un campo de sólo lectura y la marcha que garantice que las mutaciones ocurren en una copia se hace cargo.

Por desgracia, la especificación ofrece poca orientación sobre este asunto. Es evidente que algo se rompe porque lo estamos haciendo de manera inconsistente, pero lo que el derecho que hay que hacer no es del todo claro.

Otros consejos

métodos Struct están entre líneas cuando el tipo de estructura se conoce en tiempo de compilación, y el método de llamar a través de la interfaz es lento, así respuesta es:., Debido a motivos de rendimiento

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top