2011-07-28 32 views
17

顯然,約束執行區域保證不適用於迭代器(可能是因爲它們是如何實現的以及所有的),但是這是一個錯誤還是設計? [請參閱下面的示例。]C#try-finally 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]; } 
} 

(代碼大多來自here被盜。)

+2

對於你看起來值得注意的第一個人來說......至少據我可以通過Google搜索其他參考資料得知。 –

+0

我確實發現這個https://vmccontroller.svn.codeplex.com/svn/VmcController/VmcServices/DetectOpenFiles.cs代碼片段,其中毫無戒心的作者不會獲得他認爲他正在獲得的CER。 –

+0

@布萊恩:哈哈,不錯。我認爲這是大多數人不經常使用的東西,而那些可能已經直觀地瞭解,而沒有真正想過的人。只是我的猜測,但。 – Mehrdad

回答

14

好了,我不知道這是否錯誤或在覈證減排量並沒有設計來處理只是一個真的怪異的邊緣情況。

所以這裏是相關的代碼。

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沒有呼叫,儘管什麼反光說。那麼它潛伏在哪裏?那麼,它是在那裏自動生成的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(); 
} 

所以讓我們稍微仔細地檢查這一點。我們現在在try區塊內撥打PrepareConstainedRegions電話,而不是在應有的區域外。我們的StackOverflow呼叫已從finally區塊移至try區塊。

根據documentationPrepareConstrainedRegions必須立即在try塊之前。所以假設如果放在其他地方,它是無效的。

但是,即使C#編譯器得到該部分正確的東西仍然會被搞砸,因爲try塊不受限制。只有catchfinallyfault塊。你猜怎麼着? StackOverflow呼叫從finally區塊移至try區塊!

+0

+1,很好的答案,但什麼是「故障」塊? **編輯:**沒關係這是與[yield]相關的東西(http://startbigthinksmall.wordpress.com/2008/06/09/behind-the-scenes-of-the-c-yield-keyword/) –

+1

@Jalal:不,它*與收益無關。這幾乎就是'catch {... throw; }',沒有額外的'throw'語句。 (因爲它們幾乎是一回事,所以它不是C#的一個特性。) – Mehrdad

+1

@Jalal一個錯誤塊就像一個finally塊,但只有在控制因異常而離開時纔會運行。 [這是一點點]。(http://www.simple-talk.com/community/blogs/simonc/archive/2011/02/09/99250.aspx)編譯器_uses it_在其實現一個枚舉狀態機,但它並不特定於yield關鍵字。 –