C語言基礎C#之11:PreferforeachLoops

字號:

C#的foreach表達式不僅僅是do、while、for循環(huán)的變體。它為你擁有的任何集合生成的迭代代碼。它的定義和.Net框架中的集合接口相綁定,C#編譯器為這個特定類型的集合生成的代碼。當你迭代集合的時候,使用foreach代替其它循環(huán)結構??催@些循環(huán):
    int[] foo = new int[100];
    // Loop 1:
    foreach (int i in foo)
    Console.WriteLine(i.ToString());
    // Loop 2:
    for (int index = 0;index < foo.Length;index++)
    Console.WriteLine(foo[index].ToString());
    // Loop 3:
    int len = foo.Length;
    for (int index = 0;index < len;index++)
    Console.WriteLine(foo[index].ToString());
    對于當前的和未來的c#編譯器(版本1.1或者更高),Loop 1是的,它甚至是打字最少的,你個人的生產(chǎn)力能夠提高。(C#1.0的編譯器為Loop1生成比較慢的代碼,因此在那個版本里面Loop2是的)。Loop 3 ,這個多數(shù)C和C++程序員會認為效的結構,是最壞的選擇。通過將長度變量提升到循環(huán)之外,你做了如下的改變:妨礙了JIT編譯器移除在循環(huán)內部檢測范圍的機會。
    C#代碼運行在安全的、托管環(huán)境上。每次內存分配都被檢查,包括數(shù)組的索引。通過使用一些庫,Loop 3實際的代碼有點像下面這樣:
    // Loop 3, as generated by compiler:
    int len = foo.Length;
    for (int index = 0;index < len;index++)
    {
    if (index < foo.Length)
    Console.WriteLine(foo[index].ToString());
    else
    throw new IndexOutOfRangeException();
    }
    JITC#編譯器就是不喜歡你試圖這樣幫助它。你將對長度這個屬性的訪問提升到循環(huán)外部來的嘗試,只會使JIT編譯器做更多的工作甚至生成更慢的代碼。CLR的一個保障就是你不能編寫超出你的變量所擁有的內存的代碼。運行時在訪問每個特定的數(shù)組元素之前,會為實際的數(shù)組邊界生成一個測試(不是你的len變量)。你以2遍的代價得到了一次邊界的檢測。
    Examda提示: 需要在循環(huán)的每次重復時,為數(shù)組的索引檢查付出代價,并且做了2次。Loop 1 和Loop 2更快的原因是C#編譯器和JIT編譯器能夠驗證循環(huán)的邊界,保證安全。任何時候,當循環(huán)變量不是數(shù)組長度時,邊界檢查會在每次重復時執(zhí)行。
    在原來的C#編譯器下,foreach和數(shù)組生成非常慢代碼的原因在于裝箱,這會在Item 17中覆蓋。數(shù)組是類型安全的?,F(xiàn)在foreach為數(shù)組生成了與其他集合不同的IL。數(shù)組的版本沒有使用IEnumerator接口,該接口要求裝箱和拆箱操作:
    IEnumerator it = foo.GetEnumerator();
    while (it.MoveNext())
    {
    int i = (int)it.Current; // box and unbox here.
    Console.WriteLine(i.ToString());
    }
    相反,foreach表達式為數(shù)組生成了這樣的結構:
    for (int index = 0;index < foo.Length;index++)
    Console.WriteLine(foo[index].ToString());
    foreach總是生成的代碼。你沒必要去記住哪個構造生成了效的循環(huán)結構:foreach和編譯器會為你做這個工作。
    如果效率對你來說還不夠的話,那么考慮語言的互操作性。這個世界上的一些家伙(是的,他們中的多數(shù)使用其他的編程語言)強烈的相信索引變量以1開始,而不是0.無論我們如何努力,都不能打破他們的這個習慣。.Net小組就曾經(jīng)嘗試過。你不得不在C#中寫下這樣的初始化,以便獲得一個以其他非0開始的數(shù)組。
    // Create a single dimension array.
    // Its range is [ 1 .. 5 ]
    Array test = Array.CreateInstance(typeof(int), new int[] { 5 }, new int[] { 1 });
    這個代碼足以使任何人畏縮從而僅僅編寫以0開始的數(shù)組了。但是有的人非常頑固。盡你所能的努力,他們還會從1開始數(shù)。幸運的是,這是你可以強加給編譯器的很多問題之一。使用foreach來迭代test數(shù)組:
    foreach (int j in test)
    Console.WriteLine(j);
    Foreach表達式知道如何來檢查數(shù)組的上限和下限,因此你沒必要——同時這也和手工編碼一樣快,而不用管一些人決定用什么不同的下限。
    foreach為你加入了其他語言的好處。循環(huán)變量是只讀的:使用foreach時,你不能替換一個集合中的對象。同時,具有向正確類型的直接轉換。如果集合中包含有錯誤類型的對象,迭代就會拋出異常。
    Examda提示: foreach對于多維數(shù)組也提供了相似的好處。假設你正在創(chuàng)建一個棋盤。你可能寫下這樣的2個片段:
    private Square[,] theBoard = new Square[ 8, 8 ];
    // elsewhere in code:
    for ( int i = 0; i < theBoard.GetLength(0); i++ )
    for( int j = 0; j < theBoard.GetLength(1); j++ )
    theBoard[ i, j ].PaintSquare( );
    相反,你可以這樣來簡化繪制棋盤:
    foreach( Square sq in theBoard )
    sq.PaintSquare( );
    foreach表達式生成合適的代碼在該數(shù)組的所有維度上進行迭代。如果你將來會制作一個3D的棋盤,foreach還是能工作。其它的循環(huán)就需要進行修改了:
    for ( int i = 0; i < theBoard.GetLength( 0 ); i++ )
    for( int j = 0; j < theBoard.GetLength( 1 ); j++ )
    for( int k = 0; k < theBoard.GetLength( 2 ); k++ )
    theBoard[ i, j, k ].PaintSquare( );
    事實上,在一個多維數(shù)組上,即使每一維有不同的下限,foreach還是能夠工作的。我不想寫下那種代碼,甚至是一個例子。但是當其他人寫下那樣的集合代碼時,foreach能夠對付。
    如果你以后發(fā)現(xiàn)需要修改來自數(shù)組的下級的數(shù)據(jù)結構,foreach也賦予你保持很多代碼完整的彈性。我們以一個簡單的數(shù)組來進行討論:
    int [] foo = new int[100];
    假設,在以后的某個時候,你意識到需要不是那么容易被數(shù)組類處理的能力。你可以很簡單的將數(shù)組修改成ArrayList:
    // Set the initial size:
    ArrayList foo = new ArrayList( 100 );
    同時,任何對于循環(huán)的手工編碼都會被破壞:
    int sum = 0;
    // won't compile: ArrayList uses Count, not Length
    for (int index = 0;index < foo.Length;index++)
    // won't compile: foo[ index ] is object, not int.
    sum += foo[index];
    然而,foreach循環(huán)編譯成不同的代碼:它自動將每個操作數(shù)轉換成適合的類型。不需要你做任何轉換。轉換不僅僅是為了對集合類進行標準化,或者是為了任何集合類型都可以與foreach一起使用。
    如果你支持.Net環(huán)境下對集合的規(guī)則,你的類型的用戶可以使用foreach來對所有的成員進行迭代。對于foreach表達式,將它考慮成一個集合類型,一個必須擁有至少一個屬性的類。公公的GetEnumerator()方法的存在形成了一個集合類。直接實現(xiàn)IEnumerable接口創(chuàng)建一個集合類型。實現(xiàn)IEnumerator接口創(chuàng)建一個集合類型。Foreach和它們中的任何一個都可以一起使用。
    foreach有一個附加的好處,就是忽略了資源管理。IEnumerable接口包含了一個方法:GetEnumerator()。用在一個enumerable 類型上的foreach表達式生成下列的代碼,同時,它進行了一些優(yōu)化:
    IEnumerator it = foo.GetEnumerator() as IEnumerator;
    using (IDisposable disp = it as IDisposable)
    {
    while (it.MoveNext())
    {
    int elem = (int)it.Current;
    sum += elem;
    }
    }
    如果編譯器能判定這個enumerator是否實現(xiàn)了IDisposable,它自動優(yōu)化處于最終括號中的代碼。但是對于你來說,看到這一點非常重要,無論怎么回事,foreach都生成正確的代碼。
    foreach是一個很通用的表達式。它為數(shù)組的上限和下限生成正確的代碼,對多維數(shù)組進行迭代,將操作數(shù)強制轉換成正確的類型(使用的構造),而且,最主要的是,生成效的循環(huán)結構。它是對集合進行迭代的方法。有了它,你可以創(chuàng)建更持久的代碼,在首先出現(xiàn)的地方進行編寫也是容易的。使用它,是一個小小的生產(chǎn)力的提升,但是隨著時間的變化,它的效果會增加的。