C# опитват-накрая CER прекъсват ли итераторите?

Очевидно гаранциите за регион на ограничено изпълнение не се прилагат за итератори (вероятно поради начина, по който са внедрени и всичко останало), но това грешка ли е или е проектирано? [Вижте примера по-долу.]

т.е. какви са правилата за 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]; }
}

(Кодът е откраднат предимно от тук .)


person user541686    schedule 28.07.2011    source източник
comment
Колкото и да си струва, вие изглежда сте първият човек, който забелязва това... поне доколкото мога да разбера от гугъл за други препратки към него.   -  person Brian Gideon    schedule 28.07.2011
comment
Намерих това vmccontroller.svn.codeplex.com/svn/VmcController/ VmcServices/ фрагмент от код, в който нищо неподозиращият автор няма да получи CER, който си мисли, че получава.   -  person Brian Gideon    schedule 28.07.2011
comment
@Brian: Хаха, хубаво. Мисля, че това е нещо, което повечето хора не използват много често, а тези, които го използват, вероятно вече знаят интуитивно, без всъщност да са се замисляли за това. Само мое предположение обаче.   -  person user541686    schedule 28.07.2011
comment
Не знам. Откритието ти е доста езотерично. Хората, които са работили върху CERs или итератори, може да не са се сетили за този ръбов случай в крайна сметка. В противен случай може да очаквате грешка на компилатора или предупреждение като това, което получавате, когато се опитате да поставите yield return в try-catch. Просто казвам...   -  person Brian Gideon    schedule 29.07.2011


Отговори (1)


Е, не знам дали това е грешка или просто наистина странен крайен случай, в който CER не са предназначени да се справят.

И така, ето съответния код.

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

Когато това се компилира и се опитаме да го декомпилираме в C# с Reflector, получаваме това.

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. И така, къде се крие? Е, той е точно там в автоматично генерирания MoveNext метод на IEnumerator. Този път 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 блоковете не са ограничени. Само catch, finally и fault блокове са. И познай какво? Това StackOverflow повикване беше преместено от блок finally в блок try!

person Brian Gideon    schedule 28.07.2011
comment
+1, хубав отговор, но какво е fault блок? Редактиране: няма значение, че е нещо свързано с доходност - person Jalal Said; 28.07.2011
comment
@Jalal: Не, не е свързано с добива. Това е почти само catch { ... throw; }, без допълнителния оператор throw. (Тъй като те са почти едно и също нещо, това не е характеристика на C#.) - person user541686; 28.07.2011
comment
@Jalal Блокът за грешка е като окончателен блок, но се изпълнява само когато контролът напусне поради изключение. Ето малко повече за това . Компилаторът го използва в своята реализация на изброима държавна машина, но не е специфичен за ключовата дума yield. - person Chris Hannon; 28.07.2011