在迭代器中执行c# try-finally cer中断

本文关键字:try-finally cer 中断 执行 迭代器 | 更新日期: 2023-09-27 18:04:05

显然,约束执行区域保证不适用于迭代器(可能是因为它们的实现方式),但这是一个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]; }
}

(代码大部分是从这里偷来的)

在迭代器中执行c# try-finally cer中断

嗯,我不知道这是一个bug还是一个真的奇怪的边缘情况,cer不是设计来处理的。

这里是相关代码

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

当它被编译时,我们试图用Reflector将它反编译成c#,我们得到了这个

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

现在等一下!反射器把这一切都搞砸了。这就是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 
}

请注意,实际上,尽管Reflector说了什么,但没有调用PrepareConstrainedRegions。那么它潜伏在哪里呢?它就在自动生成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();
    }
}

StackOverflow的呼叫神秘地移动到了哪里?在m_Finally2()方法中。

private void <>m__Finally2()
{
    this.<>1__state = -1;
    Program.StackOverflow();
}

让我们更仔细地检查一下。现在我们把PrepareConstainedRegions调用放在了try块中,而不是在它应该在的地方。我们的StackOverflow调用已经从finally块移动到try块。

根据文档PrepareConstrainedRegions必须紧接在try块之前。因此,假设它放在其他任何地方都是无效的。

但是,即使c#编译器得到了正确的部分,事情仍然会搞砸,因为try块不受约束。只有catchfinallyfault块是。你猜怎么着?StackOverflow调用从finally块移动到try块!