Разбиваются ли C # C try-finally CER в итераторах?

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]; }
}

(Код, в основном, украден у здесь .)

    
задан Mehrdad 28.07.2011 в 18:16
источник
  • Для чего вы стоите, кажется, первым, кто заметил это ... по крайней мере, насколько я мог судить по поиску по другим ссылкам. –  Brian Gideon 28.07.2011 в 19:48
  • Я нашел этот vmccontroller.svn.codeplex.com/svn/VmcController/VmcServices/... фрагмент кода, в котором ничего не подозревающий автор не собирается получать ССВ, который, по его мнению, он получает. –  Brian Gideon 28.07.2011 в 21:28
  • @Brian: Lol, хорошо. Я думаю, что это то, что большинство людей не используют очень часто, и те, кто, вероятно, уже знают интуитивно, даже не задумываясь об этом. Но я думаю. –  Mehrdad 28.07.2011 в 21:54
  • Я не знаю. Ваше открытие довольно эзотерическое. Люди, которые работали над ССВ или итераторами, возможно, не подумали об этом случае края. В противном случае вы можете ожидать ошибки или предупреждения компилятора, которые вы получаете, когда пытаетесь вернуть доходность в try-catch. Просто говорю... –  Brian Gideon 28.07.2011 в 22:05

1 ответ

15

Ну, я не знаю, является ли это ошибкой или просто действительно странным крайним случаем, в котором 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. Так где же это скрывается? Ну, это прямо в автоматически сгенерированном методе IEnumerator 's co_de%. На этот раз 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();
    }
}

И куда таинственным образом переместился этот вызов MoveNext ? Прямо внутри метода StackOverflow .

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

Итак, давайте рассмотрим это немного более внимательно. Теперь у нас есть вызов m_Finally2() внутри блока PrepareConstainedRegions , а не снаружи, где он должен быть. И наш вызов try переместился из блока StackOverflow в блок finally .

Согласно документации try должен непосредственно предшествовать блоку PrepareConstrainedRegions . Таким образом, предполагается, что он неэффективен, если размещен где-либо еще.

Но даже если бы компилятор C # правильно понял эту часть, все равно все будет испорчено, потому что блоки try не ограничены. Только блоки try , catch и finally . И угадай что? Этот вызов fault был перемещен из блока StackOverflow в блок finally !

    
ответ дан Brian Gideon 28.07.2011 в 19:24
  • +1, хороший ответ, но что такое блок отказов? Изменить: неважно, что это связано с урожаем –  Jalal Said 28.07.2011 в 19:53
  • @Jalal: Нет, это не связано с доходностью. Это в значительной степени просто поймать {... бросить; }, без дополнительной команды throw. (Так как они почти то же самое, это не особенность C #.) –  Mehrdad 28.07.2011 в 20:49
  • @Jalal Блок сбоев подобен блоку finally, но выполняется только при выходе управления из-за исключения. Здесь немного больше. Компилятор использует его при реализации перечислимого конечного автомата, но это не относится к ключевому слову yield. –  Chris Hannon 28.07.2011 в 20:54
  • @ Спасибо за ссылку;) –  Jalal Said 29.07.2011 в 08:28