C#try-finally CERs在迭代器中是否会中断?

Meh*_*dad 18 .net c# finalization cer

显然,Constrained Execution Region保证不适用于迭代器(可能是因为它们的实现方式和所有),但这是一个bug还是设计?[见下面的例子.]

关于CER与迭代器一起使用的规则是什么?

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]; }
}
Run Code Online (Sandbox Code Playgroud)

(代码大多是从这里来的.)

Bri*_*eon 15

好吧,我不知道这是一个错误还是一个非常奇怪的边缘情况,其中没有设计CERs来处理.

所以这是相关的代码.

private static IEnumerable<int> Iterate()
{
    RuntimeHelpers.PrepareConstrainedRegions();
    try { cerWorked = false; yield return 5; }
    finally { StackOverflow(); }
}
Run Code Online (Sandbox Code Playgroud)

当这个被编译并且我们尝试使用Reflector将其反编译成C#时,我们得到了这个.

private static IEnumerable<int> Iterate()
{
    RuntimeHelpers.PrepareConstrainedRegions();
    cerWorked = false;
    yield return 5;
}
Run Code Online (Sandbox Code Playgroud)

现在等一下!反射器已将此全部搞砸了.这就是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 
}
Run Code Online (Sandbox Code Playgroud)

请注意,PrepareConstrainedRegions尽管Reflector说的是,事实上还没有打电话.那么潜伏在哪里?好吧,它就在自动生成IEnumeratorMoveNext方法中.这次Reflector做对了.

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();
    }
}
Run Code Online (Sandbox Code Playgroud)

这个叫StackOverflow神秘地转移到哪里?在m_Finally2()方法内部.

private void <>m__Finally2()
{
    this.<>1__state = -1;
    Program.StackOverflow();
}
Run Code Online (Sandbox Code Playgroud)

所以让我们仔细研究一下.我们现在PrepareConstainedRegions在一个try区块内而不是外面的区域内进行调用.我们的StackOverflow电话已从一个finally街区转移到一个try街区.

根据文档 PrepareConstrainedRegions必须立即在try块之前.所以假设如果放在其他任何地方它是无效的.

但是,即使C#编译器得到了正确的部分,事情仍然会被搞砸,因为try块不受限制.只有catch,finally和,fault块.你猜怎么着?那个StackOverflow电话从一个finally块移到一个try块!