español - ¿Los C#try-finally CERs se rompen en los iteradores?
try catch finally c# (1)
Bueno, no sé si este es un error o simplemente un caso realmente extraño en el que los CER no fueron diseñados para manejar.
Así que aquí está el código pertinente.
private static IEnumerable<int> Iterate()
{
RuntimeHelpers.PrepareConstrainedRegions();
try { cerWorked = false; yield return 5; }
finally { StackOverflow(); }
}
Cuando esto se compila e intentamos descompilarlo en C # con Reflector, obtenemos esto.
private static IEnumerable<int> Iterate()
{
RuntimeHelpers.PrepareConstrainedRegions();
cerWorked = false;
yield return 5;
}
¡Ahora espera un segundo! Reflector tiene todo esto jodido. Esto es lo que realmente parece la IL.
.method private hidebysig static class [mscorlib]System.Collections.Generic.IEnumerable`1<int32> Iterate() cil managed
{
.maxstack 2
.locals init (
[0] class Sandbox.Program/<Iterate>d__1 d__,
[1] class [mscorlib]System.Collections.Generic.IEnumerable`1<int32> enumerable)
L_0000: ldc.i4.s -2
L_0002: newobj instance void Sandbox.Program/<Iterate>d__1::.ctor(int32)
L_0007: stloc.0
L_0008: ldloc.0
L_0009: stloc.1
L_000a: br.s L_000c
L_000c: ldloc.1
L_000d: ret
}
Observe que, de hecho, no hay llamadas a PrepareConstrainedRegions
pesar de lo que dice Reflector. Entonces, ¿dónde está acechando? Bueno, está justo ahí en el método MoveNext
IEnumerator
generado automáticamente. Esta vez Reflector lo hace bien.
private bool MoveNext()
{
try
{
switch (this.<>1__state)
{
case 0:
this.<>1__state = -1;
RuntimeHelpers.PrepareConstrainedRegions();
this.<>1__state = 1;
Program.cerWorked = false;
this.<>2__current = 5;
this.<>1__state = 2;
return true;
case 2:
this.<>1__state = 1;
this.<>m__Finally2();
break;
}
return false;
}
fault
{
this.System.IDisposable.Dispose();
}
}
¿Y adónde se movió misteriosamente esa llamada a StackOverflow
? Justo dentro del método m_Finally2()
.
private void <>m__Finally2()
{
this.<>1__state = -1;
Program.StackOverflow();
}
Así que examinemos esto un poco más de cerca. Ahora tenemos nuestra llamada PrepareConstainedRegions
dentro de un bloque try
lugar de afuera donde debería estar. Y nuestra llamada StackOverflow
ha movido de un bloque final a un bloque de try
.
De acuerdo con la documentation PrepareConstrainedRegions
debe preceder inmediatamente al bloque try
. Entonces, se supone que es ineficaz si se coloca en otro lugar.
Pero, incluso si el compilador de C # tuviera la parte correcta, las cosas todavía se podrían estropear porque los bloques de try
no están restringidos. Solo se catch
, finally
, y los bloques de fault
son. ¿Y adivina qué? ¡Esa llamada StackOverflow
se movió de un bloque final a un bloque try
!
Aparentemente, las garantías de la Región de Ejecución Restringida no se aplican a los iteradores (probablemente debido a la forma en que se implementan y todo), pero ¿es esto un error o por diseño? [Vea el ejemplo a continuación.]
es decir, ¿cuáles son las reglas sobre los CER que se utilizan con los iteradores?
using System.Runtime.CompilerServices;
using System.Runtime.ConstrainedExecution;
class Program
{
static bool cerWorked;
static void Main(string[] args)
{
try
{
cerWorked = true;
foreach (var v in Iterate()) { }
}
catch { System.Console.WriteLine(cerWorked); }
System.Console.ReadKey();
}
[ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)]
unsafe static void StackOverflow()
{
Big big;
big.Bytes[int.MaxValue - 1] = 1;
}
static System.Collections.Generic.IEnumerable<int> Iterate()
{
RuntimeHelpers.PrepareConstrainedRegions();
try { cerWorked = false; yield return 5; }
finally { StackOverflow(); }
}
unsafe struct Big { public fixed byte Bytes[int.MaxValue]; }
}
(Código mayormente robado de here .)