18

显然,受约束的执行区域保证不适用于迭代器(可能是因为它们是如何实现的),但这是一个错误还是设计使然?[见下面的例子。]

即与迭代器一起使用的 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]; }
}

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

4

1 回答 1

15

好吧,我不知道这是一个错误还是只是一个非常奇怪的边缘情况,其中 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 
}

请注意,实际上,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();
    }
}

那个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块!

于 2011-07-28T17:24:15.400 回答