C#裡for和foreach的區別

來源:互聯網
上載者:User

很久很久以前,就聽說,for和foreach是不一樣的(不僅僅是文法),在網上也看到很很多說明的文章。
但從自己寫的代碼中來看,很難看出區別在那,因為大多數時候,都是用for或者foreach
對一個數組結構的類進行遍曆操作。

某天突然想弄清楚這個問題,於是小小的分析了一下,看看下段代碼:

public void For()
{
 string[] array = new string[]{"111","222","333"};

 for(int i = 0; i < array.Length; i++)
 {
  Console.WriteLine(array[i]);
 }
}
////Results :
111
222
333

public void ForeachOnArray()
{
 string[] array = new string[]{"111","222","333"};

 foreach(string s in array)
 {
  Console.WriteLine(s);
 }
}
////Results :
111
222
333

一樣的輸入結果咯!來看看ILdasm告訴我們的:

For:

 

Foreach:

奇怪,雖然所調用的指令不不同,但也是差不多,都是根據length來對資料進行迴圈。(與網上說得有點不一樣哦)
(在這裡主要不同的是foreach裡自動對array進行index + 1的操作來迴圈,而for則是自己的代碼控制的。)

再加點代碼,在迴圈中試圖更改所操作的值:
array = new string[]{"AAA","BBB","CCC"};

///For() result:
111
BBB
CCC

///ForeachOnArray() result:
111
222
333

不一樣了把,看來在foreach內部的迴圈中對源的更改不是即使生效的!

如果試著更改當前操作的數組內的值:
For() :
array[i] = "changed"; //OK

ForeachOnArray() :
s = "Changed";  //編譯時間Error,提示: “Cannot assign to 's' because it is read-only”

如果改為:
ForeachOnArray() :
array[2] = "Changed"; //在Foreach()內部無效,跳出Foreach()迴圈時更改才生效。

恩,以上的區別是很顯而易見了,
結論:在Foreach(...)迴圈裡盡量不要更改操作的源,
在For(...)迴圈裡則無所謂(看起來For跟do.While.的迴圈更類似,核心僅僅是判斷)

但這就是全部的真實了嗎?

NO,來考慮下我們經常在DataRowCollection( 即 DataTable.DataRow )上做的迴圈--它可不是一個數組,
而根據MS的參考,能在foreach上做迴圈的只能是實現了IEnumerable介面的類. (事實上,System.Array也是實現了IEnumerable介面的)

恩,現在拋開數組,來做一個在IEnumerable上的迴圈,先編寫如下的實現了IEnumberable介面的類E:

 public class E : IEnumerable
 {
  private InnerEnumerator inner;

  public E(string[] array)
  {
   this.inner = new InnerEnumerator(array);
  }

  #region IEnumerable Members

  public IEnumerator GetEnumerator()
  {
   return this.inner;
  }

  #endregion

  private class InnerEnumerator : IEnumerator, IDisposable
  {
   private string[] s;
   private int currentIndex;

   public InnerEnumerator(string[] array)
   {
    this.s = array;
    this.Reset();
   }

   #region IEnumerator Members

   //Reset index to original
   public void Reset()
   {
    this.currentIndex = s.Length - 1;
   }

   //Get Current object inner
   public object Current
   {
    get
    {
     object o = this.s[this.currentIndex];
     this.currentIndex--;
     return o;
    }
   }

   //Is there has any other object in the array?
   public bool MoveNext()
   {
    if(this.currentIndex < 0)
    {
     return false;
    }
    return true;
   }

   #endregion

   #region IDisposable Members

   //Dispose Here
   public void Dispose()
   {
    Console.WriteLine("Dispose here !");
   }

   #endregion
  }
 }

接下來,拿這個類做測試,在上面做迴圈看看:(過程與For()和ForeachOnArray()大致一樣)

public void ForeachOnIEnumerable()
{
 string[] array = new string[]{"111","222","333"};
 E e = new E(array);

 foreach(string s in e)
 {
  Console.WriteLine(s);
 }
}

//result:
333
222
111
Dispose here !

差異出現了,這次是按照倒序的方式,而且看樣子還是自動調用了Dispose方法!
(NOTE : 我們可沒有自己編寫調用Dispose()方法的代碼!)

還是老規矩,用ILdasm看看實際執行的IL代碼:

果然,多了很多不一樣的東東
try
{
}
finally
{
}

分析:
順序:
1.調用e.GetEnumerator()方法,擷取一個Enumerator執行個體
 等同於代碼:
  IEnumerator ienumerator = e.GetEnumertor();

2.在這個Enumerator執行個體上調用GetCurrent()方法,將擷取到的object 通過 castclass 指命
  轉換為我們在代碼中定義的類型string
 即等同與代碼: 
  string s = (string)ienumerator.GetCurrent();
 (NOTE : 如果失敗,會拋出InvalidCastException異常,表明轉換失敗,類型不符)
 (即:如果我們這裡改為:foreach(int s in array),就會發生運行時的錯誤,拋出InvalidCastException異常,
 這就是castclass關鍵字的作用)

3.對這個GetCurrent()所得的object做自己想要得處理
 在這裡就是我們自己編寫的代碼:
  Console.WriteLine(s);

4.在擷取得IEnumerator上調用MoveNext(),如果true,則進入下次迴圈(跳轉到2),如果為false,則跳出迴圈
 等同於代碼:
  if(ienumerator.MoveNext())
  {
   goto step-2;
  }
  else
  {
   goto finally;
  }

finally塊代碼:
1.判斷前面擷取的ienumerator是否實現了IDisposable介面,true則繼續,false則break;
 等同樣代碼:
  if(!(ienumerator is IDispose))
  {
   break; //over whole
  }
2.調用IDisposable上的Dispose()方法(這就是為什麼輸入最後有一句"Dispose here !"了)
 因為我們寫在Dispose()中的方法在這裡被自動調用了!!!
 等同於代碼:
  ienumerator.Dispose()

分析:
。。。
這與前面在Array上做的迴圈(ForeachOnArray())可大不一樣,是兩段完全不同的代碼!

綜合上面,得出如下結論:
1.for迴圈並不依賴於數組或其他形式的組式資料結構,只是簡單的
 在調用了代碼後,進行一個判斷,判斷是否要繼續。
 (非常類似於do..while和while迴圈--在這裡不作具體分析了^_^~~)
2.foreach迴圈如果作用在一個基於System.Array的類型之上的數組的話,編譯器會自動最佳化成與for迴圈非常類似
 的代碼,只是調用的指命有細微的差別,並且檢查(包括編譯階段和運行時)會比for嚴格的多
3.foreach迴圈作用在一個非System.Array類型上(且一定要是實現了IEnumerable介面的類),會先調用
 IEnumerable.GetEnumerator()方法擷取一個Enumertor執行個體,再在擷取的Enumertor執行個體上調用
 GetCurrent()和MoveNext()方法,最後判斷如果Enumertor執行個體如果實現了IDispose介面,就自動調用
 IDispose.Dispose()方法!

那麼我們應該分別在那些地方用for和foreach捏
建議:
1.在有對所迴圈的本體(System.Array)做賦值操作時,盡量不要用Foreach()。
2.foreach比for更靈活。(可在MoveNext()和GetCurrent()裡編寫自己的代碼).
 自己編寫的類如果實現了IEnumerable介面的話,就可以用foreach迴圈了,而不管內部是否有一個真實的數組,
 並且可以自訂迴圈的規則。
3.從OO的原則看,foreach迴圈更適於多數情況的使用
 (事實上,foreach的實現是典型的Iterator模式,下面有簡單的描述它的好處)
 想用統一的調用迴圈介面時,foreach是最佳的選擇
 (MS有很多類就是這樣的,例如前面提到的DataRowCollection.)
 

 

補充說明:
/////////////////////////////////////////////////////////////////////////////////////////////
isinit:

如果再做進一步的實驗的話,可以發現,其實這裡的isinit就是C#的is關鍵字(而as關鍵字的核心也是isinit這個作業碼)
擷取反過來說,is,as關鍵字在IL代碼裡表現為這個isinit.
isinst不會觸發異常,只是判斷類型是否相容。

/////////////////////////////////////////////////////////////////////////////////////////////
castclass:

castclass用於強制轉換:
castclass指命會引發如下一樣:
InvalidCastException - Specified cast in not valid

而如果Foreach的作用在一個string[]類型的數組( 例如上面的代碼改為 : foreach(int s in array) )
這種錯誤在編譯時間就能檢測出來,提示:Cannot convert type 'string' to 'int'

/////////////////////////////////////////////////////////////////////////////////////////////
關於Iterator模式:

Iterator模式是用於遍曆集合類的標準存取方法。它可以把訪問邏輯從不同類型的集合類中抽象出來,
從而避免向用戶端暴露集合的內部結構。

特點:[引用]
"要確保遍曆過程順利完成,必須保證遍曆過程中不更改集合的內容,
因此,確保遍曆可靠的原則是只在一個線程中使用這個集合,或者在多線程中對遍曆代碼進行同步。"
這也能解釋為什麼C#.NET在Foreach上要做嚴格的使用限制,而For則沒有。

Iterator模式參考:
http://www.emagister.cn/cursos-java%E6%BA%90%E7%A0%81%E5%88%86%E6%9E%90%EF%BC%9A%E6%B7%B1%E5%85%A5%E6%8E%A2%E8%AE%A8iterator%E6%A8%A1%E5%BC%8F-simcour-1636693.htm

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.