tutorial tag que para manager google funciona español como .net struct closures mutable ienumerator

.net - tag - ¿Por qué la captura de una variable de estructura mutable dentro de un cierre dentro de una instrucción using cambia su comportamiento local?



google tag manager tutorial español (4)

Actualización : Bueno, ahora me he ido y lo hice: presenté un informe de error con Microsoft sobre esto, ya que dudo seriamente de que sea un comportamiento correcto. Dicho esto, todavía no estoy 100% seguro de qué creer con respecto a esta pregunta ; entonces puedo ver que lo que es "correcto" está abierto a algún nivel de interpretación.

Mi sensación es que Microsoft aceptará que esto es un error, o bien responderá que la modificación de una variable de tipo de valor variable dentro de una declaración using constituye un comportamiento indefinido.

Además, por lo que vale, al menos tengo una suposición sobre lo que está sucediendo aquí. Sospecho que el compilador está generando una clase para el cierre, "levantando" la variable local a un campo de instancia de esa clase; y dado que está dentro de un bloque de using , está haciendo que el campo sea de readonly . Como LukeH señaló en un comentario a la otra pregunta , esto evitaría que las llamadas a métodos como MoveNext modifiquen el campo en sí (en su lugar afectarían una copia).

Nota: He acortado esta pregunta para facilitar la lectura, aunque todavía no es exactamente corta. Para la pregunta original (más larga) en su totalidad, consulte el historial de edición.

He leído lo que creo que son las secciones relevantes de ECMA-334 y no puedo encontrar una respuesta concluyente a esta pregunta. Formularé la pregunta primero, luego proporcionaré un enlace a algunos comentarios adicionales para aquellos que estén interesados.

Pregunta

Si tengo un tipo de valor mutable que implementa IDisposable , puedo (1) llamar a un método que modifique el estado del valor de la variable local dentro de una instrucción using y el código se comporte como espero. Una vez que capturo la variable en cuestión dentro de un cierre dentro de la instrucción using , sin embargo, (2) las modificaciones al valor ya no son visibles en el ámbito local.

Este comportamiento solo es aparente en el caso en que la variable se captura dentro del cierre y dentro de una instrucción using ; no es aparente cuando solo está presente una ( using ) o la otra condición (cierre).

¿Por qué la captura de una variable de un tipo de valor mutable dentro de un cierre dentro de una sentencia using cambia su comportamiento local?

A continuación se muestran ejemplos de código que ilustran los elementos 1 y 2. Ambos ejemplos utilizarán la siguiente demostración Tipo de valor Mutable :

struct Mutable : IDisposable { int _value; public int Increment() { return _value++; } public void Dispose() { } }

1. Mutar una variable de tipo de valor dentro de un bloque de using

using (var x = new Mutable()) { Console.WriteLine(x.Increment()); Console.WriteLine(x.Increment()); }

El resultado del código de salida:

0 1

2. Capturar una variable de tipo de valor dentro de un cierre dentro de un bloque de using

using (var x = new Mutable()) { // x is captured inside a closure. Func<int> closure = () => x.Increment(); // Now the Increment method does not appear to affect the value // of local variable x. Console.WriteLine(x.Increment()); Console.WriteLine(x.Increment()); }

Las salidas de código anteriores:

0 0

Más comentarios

Se ha observado que el compilador Mono proporciona el comportamiento que espero (los cambios en el valor de la variable local aún son visibles en el caso de using + cierre). No está claro si este comportamiento es correcto o no.

Para algunos más de mis pensamientos sobre este tema, mira here .


El problema es que el enumerador se almacena en otra clase, por lo que cada acción funciona con una copia del enumerador.

[CompilerGenerated] private sealed class <>c__DisplayClass3 { // Fields public List<int>.Enumerator enumerator; // Methods public int <Main>b__1() { return this.enumerator.Current; } } public static void Main(string[] args) { List<int> <>g__initLocal0 = new List<int>(); <>g__initLocal0.Add(1); <>g__initLocal0.Add(2); <>g__initLocal0.Add(3); List<int> list = <>g__initLocal0; Func<int> CS$<>9__CachedAnonymousMethodDelegate2 = null; <>c__DisplayClass3 CS$<>8__locals4 = new <>c__DisplayClass3(); CS$<>8__locals4.enumerator = list.GetEnumerator(); try { if (CS$<>9__CachedAnonymousMethodDelegate2 == null) { CS$<>9__CachedAnonymousMethodDelegate2 = new Func<int>(CS$<>8__locals4.<Main>b__1); } while (CS$<>8__locals4.enumerator.MoveNext()) { Console.WriteLine(CS$<>8__locals4.enumerator.Current); } } finally { CS$<>8__locals4.enumerator.Dispose(); } }

Sin la lambda, el código está más cerca de lo que cabría esperar.

public static void Main(string[] args) { List<int> <>g__initLocal0 = new List<int>(); <>g__initLocal0.Add(1); <>g__initLocal0.Add(2); <>g__initLocal0.Add(3); List<int> list = <>g__initLocal0; using (List<int>.Enumerator enumerator = list.GetEnumerator()) { while (enumerator.MoveNext()) { Console.WriteLine(enumerator.Current); } } }

IL específico

L_0058: ldfld valuetype [mscorlib]System.Collections.Generic.List`1/Enumerator<int32> Machete.Runtime.Environment/<>c__DisplayClass3::enumerator L_005d: stloc.s CS$0$0001 L_005f: ldloca.s CS$0$0001


Es un error conocido; lo descubrimos hace un par de años. La solución podría romperse, y el problema es bastante oscuro; estos son puntos en contra de arreglarlo. Por lo tanto, nunca se ha priorizado lo suficiente como para realmente arreglarlo.

Esto ha estado en mi cola de posibles temas de blog desde hace un par de años; tal vez debería escribirlo.

Y, por cierto, su conjetura en cuanto al mecanismo que explica el error es completamente precisa; buena depuración psíquica allí.

Por lo tanto, sí, error conocido, pero gracias por el informe independientemente!


Esto tiene que ver con la forma en que se generan y utilizan los tipos de cierre. Parece que hay un error sutil en la forma en que csc usa estos tipos. Por ejemplo, aquí está la IL generada por las gmcs de Mono al invocar MoveNext ():

IL_0051: ldloc.3 IL_0052: ldflda valuetype [mscorlib]System.Collections.Generic.List`1/Enumerator<int32> Foo/''<Main>c__AnonStorey0''::enumerator IL_0057: call instance bool valuetype [mscorlib]System.Collections.Generic.List`1/Enumerator<int32>::MoveNext()

Tenga en cuenta que está cargando la dirección del campo, lo que permite que la llamada al método modifique la instancia del tipo de valor almacenado en el objeto de cierre. Esto es lo que consideraría un comportamiento correcto, y esto da como resultado que los contenidos de la lista se enumeren muy bien.

Esto es lo que genera csc:

IL_0068: ldloc.3 IL_0069: ldfld valuetype [mscorlib]System.Collections.Generic.List`1/Enumerator<int32> Tinker.Form1/''<>c__DisplayClass3''::enumerator IL_006e: stloc.s 5 IL_0070: ldloca.s 5 IL_0072: call instance bool valuetype [mscorlib]System.Collections.Generic.List`1/Enumerator<int32>::MoveNext()

Entonces, en este caso, es tomar una copia de la instancia de tipo de valor e invocar el método en la copia. No debería sorprender por qué esto no te lleva a ninguna parte. La llamada get_Current () es similarmente incorrecta:

IL_0052: ldloc.3 IL_0053: ldfld valuetype [mscorlib]System.Collections.Generic.List`1/Enumerator<int32> Tinker.Form1/''<>c__DisplayClass3''::enumerator IL_0058: stloc.s 5 IL_005a: ldloca.s 5 IL_005c: call instance !0 valuetype [mscorlib]System.Collections.Generic.List`1/Enumerator<int32>::get_Current() IL_0061: call void class [mscorlib]System.Console::WriteLine(int32)

Dado que el estado del enumerador que está copiando no ha tenido MoveNext () llamado, get_Current () aparentemente devuelve el default(int) .

En resumen: csc parece tener errores. ¡Es interesante que Mono consiguiera este derecho mientras que MS.NET no lo hizo!

... Me encantaría escuchar los comentarios de Jon Skeet sobre esta particular rareza.

En una discusión con brajkovic en #mono, determinó que la especificación del lenguaje C # no detalla cómo se debe implementar el tipo de cierre, ni cómo deben traducirse los accesos de los locales que se capturan en el cierre. Una implementación de ejemplo en la especificación parece usar el método de "copia" que usa csc. Por lo tanto, cualquiera de los resultados del compilador puede considerarse correcto según la especificación del lenguaje, aunque yo argumentaría que csc debería al menos copiar el local de nuevo al objeto de cierre después de la llamada al método.


EDITAR - Esto es incorrecto, no leí la pregunta con cuidado.

Colocar la estructura en un cierre causa una asignación. Las asignaciones en tipos de valores dan como resultado una copia del tipo. Entonces, ¿qué está pasando? ¿Está creando un nuevo Enumerator<int> y Current en ese enumerador devolverá 0?

using System; using System.Collections.Generic; class Program { static void Main(string[] args) { List<int> l = new List<int>(); Console.WriteLine(l.GetEnumerator().Current); } }

Resultado: 0