1. 程式人生 > >【轉】編寫高質量代碼改善C#程序的157個建議——建議17:多數情況下使用foreach進行循環遍歷

【轉】編寫高質量代碼改善C#程序的157個建議——建議17:多數情況下使用foreach進行循環遍歷

else enume spa 開閉 next str items tro var

建議17:多數情況下使用foreach進行循環遍歷

由於本建議涉及集合的遍歷,所以在開始講解本建議之前,我們不妨來設想一下如何對結合進行遍歷。假設存在一個數組,其遍歷模式可以采用依據索引來進行遍歷的方法;又假設存在一個HashTable,其遍歷模式可能是按照鍵值來進行遍歷。無論是哪個集合,如果他們的遍歷沒有一個公共的接口,那麽客戶端在進行遍歷時,相當於是對具體類型進行了編碼。這樣一來,當需求發生變化時,必須修改我們的代碼。而且,由於客戶端代碼過多地關註了集合內部的實現,代碼的可移植性就會變得很差,這直接違反了面向對象的開閉原則。於是,叠代器模式就誕生了。現在,不要管FCL中如何實現該模式的,我們先來實現一個自己的叠代器模式。

     /// <summary>
        /// 要求所有的叠代器全部實現該接口
        /// </summary>
        interface IMyEnumerator
        {
            bool MoveNext();
            object Current { get; }
        }

        /// <summary>
        /// 要求所有的集合實現該接口
        /// 這樣一來,客戶端就可以針對該接口編碼,
        /// 而無須關註具體的實現
        /// </summary>
interface IMyEnumerable { IMyEnumerator GetEnumerator(); int Count { get; } } class MyList : IMyEnumerable { object[] items = new object[10]; IMyEnumerator myEnumerator; public object this[int i] {
get { return items[i]; } set { this.items[i] = value; } } public int Count { get { return items.Length; } } public IMyEnumerator GetEnumerator() { if (myEnumerator == null) { myEnumerator = new MyEnumerator(this); } return myEnumerator; } } class MyEnumerator : IMyEnumerator { int index = 0; MyList myList; public MyEnumerator(MyList myList) { this.myList = myList; } public bool MoveNext() { if (index + 1 > myList.Count) { index = 1; return false; } else { index++; return true; } } public object Current { get { return myList[index - 1]; } } }
        static void Main(string[] args)
        {
            //使用接口IMyEnumerable代替MyList
            IMyEnumerable list = new MyList();
            //得到叠代器,在循環中針對叠代器編碼,而不是集合MyList
            IMyEnumerator enumerator = list.GetEnumerator();
            for (int i = 0; i < list.Count; i++)
            {
                object current = enumerator.Current;
                enumerator.MoveNext();
            }
            while (enumerator.MoveNext())
            {
                object current = enumerator.Current;
            }
        }

MyList模擬了一個集合類,它繼承了接口IMyEnumerable,這樣,在客戶端調用的時候,我們就可以直接調用IMyEnumerable來聲明變量,如代碼中的一下語句:

IMyEnumerable list=new MyList();

如果未來我們新增了其他的集合類,那麽針對list的編碼即使不做修改也能運行良好。在IMyEnumerable中聲明了GetEnumerator方法返回一個繼承了IMyEnumerator的對象。在MyList的內部,默認返回MyEnumerator,MyEnumerator就是叠代器的一個實現,如果對於叠代的需求有變化,可以重新開發一個叠代器(如下所示),然後在客戶端叠代的時候使用該叠代器。

            //使用接口IMyEnumerable代替MyList
            IMyEnumerable list = new MyList();
            //得到叠代器,在循環中針對叠代器編碼,而不是集合MyList
            IMyEnumerator enumerator2 = new MyEnumerator(list);
       //for調用
for (int i = 0; i < list.Count; i++) { object current = enumerator2.Current; enumerator.MoveNext(); }
       //while調用
while (enumerator.MoveNext()) { object current = enumerator2.Current; }

在客戶端的代碼中,我們在叠代的過程中分別演示了for循環和while循環,到那時因為使用了叠代器的緣故,兩個循環都沒有針對MyList編碼,而是實現了對叠代器的編碼。

理解了自己實現的叠代器模式,相當於理解了FCL中提供的對應模式。以上代碼中,在接口和類型中都加入了“My”字樣,其實,FCL中有與之相對應的接口和類型,只不過為了演示需要,增加了其中部分內容,但是大致思路是一樣的。使用FCL中相應類型進行客戶端代碼編寫,大致應該下面這樣:

            ICollection<object> list = new List<object>();
            IEnumerator enumerator = list.GetEnumerator();

for (int i = 0; i < list.Count; i++) { object current = enumerator.Current; enumerator.MoveNext(); }
while (enumerator.MoveNext()) { object current = enumerator.Current; }

但是,無論是for循環還是while循環,都有些啰嗦,於是,foreach就出現了。

            foreach (var current in list)
            {
                //省略了 object current = enumerator.Current;
            }

可以看到,采用foreach最大限度地簡化了代碼。它用於遍歷一個繼承了IEnumerable或IEnumerable<T>接口的集合元素。借助IL代碼,我們查看使用foreach到底發生了什麽事情:

.method private hidebysig static void  Main(string[] args) cil managed
{
  .entrypoint
  // 代碼大小       62 (0x3e)
  .maxstack  2
  .locals init ([0] class [mscorlib]System.Collections.Generic.ICollection`1<object> list,
           [1] object current,
           [2] class [mscorlib]System.Collections.Generic.IEnumerator`1<object> CS$5$0000,
           [3] bool CS$4$0001)
  IL_0000:  nop
  IL_0001:  newobj     instance void class [mscorlib]System.Collections.Generic.List`1<object>::.ctor()
  IL_0006:  stloc.0
  IL_0007:  nop
  IL_0008:  ldloc.0
  IL_0009:  callvirt   instance class [mscorlib]System.Collections.Generic.IEnumerator`1<!0> class [mscorlib]System.Collections.Generic.IEnumerable`1<object>::GetEnumerator()
  IL_000e:  stloc.2
  .try
  {
    IL_000f:  br.s       IL_001a
    IL_0011:  ldloc.2
    IL_0012:  callvirt   instance !0 class [mscorlib]System.Collections.Generic.IEnumerator`1<object>::get_Current()
    IL_0017:  stloc.1
    IL_0018:  nop
    IL_0019:  nop
    IL_001a:  ldloc.2
    IL_001b:  callvirt   instance bool [mscorlib]System.Collections.IEnumerator::MoveNext()
    IL_0020:  stloc.3
    IL_0021:  ldloc.3
    IL_0022:  brtrue.s   IL_0011
    IL_0024:  leave.s    IL_0036
  }  // end .try
  finally
  {
    IL_0026:  ldloc.2
    IL_0027:  ldnull
    IL_0028:  ceq
    IL_002a:  stloc.3
    IL_002b:  ldloc.3
    IL_002c:  brtrue.s   IL_0035
    IL_002e:  ldloc.2
    IL_002f:  callvirt   instance void [mscorlib]System.IDisposable::Dispose()
    IL_0034:  nop
    IL_0035:  endfinally
  }  // end handler
  IL_0036:  nop
  IL_0037:  call       int32 [mscorlib]System.Console::Read()
  IL_003c:  pop
  IL_003d:  ret
} // end of method Program::Main

查看IL代碼就可以看出,運行時還是會調用get_Current()MoveNext()方法。

在調用完MoveNext()方法後,如果結果是true,跳轉到循環開始處。實際上foreach循環和while循環是一樣的:

            while (enumerator.MoveNext())
            {
                object current = enumerator.Current;
            }

foreach循環除了可以提供簡化的語法外,還有另外兩個優勢:

  • 自動將代碼置入try finally塊
  • 若類型實現了IDisposable接口,它會在循環結束後自動調用Dispose方法。

轉自:《編寫高質量代碼改善C#程序的157個建議》陸敏技

【轉】編寫高質量代碼改善C#程序的157個建議——建議17:多數情況下使用foreach進行循環遍歷