程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> .NET網頁編程 >> C# >> 關於C# >> 構建可反轉排序的泛型字典類(5)--實現IEnumerable接口

構建可反轉排序的泛型字典類(5)--實現IEnumerable接口

編輯:關於C#

5. 實現IEnumerable<KeyValuePair<TKey, TValue>>接口

我們先來看看ReversibleSortedList類的定義:

public class ReversibleSortedList<TKey, TValue> :
IDictionary<TKey, TValue>, ICollection<KeyValuePair<TKey, TValue>>,
IEnumerable<KeyValuePair<TKey, TValue>>, IDictionary, ICollection, IEnumerable

它一共實現了6個接口。但從本質上來說,實現IDictionary<TKey, TValue>接口和IDictionary接口就等同於實現了以上6個接口。因為 IDictionary<TKey, TValue>繼承自 ICollection<KeyValuePair<TKey, TValue>>和 IEnumerable<KeyValuePair<TKey, TValue>>接口,而IDictionary 接口繼承自ICollection和IEnumerable接口。下面我們來看一下這些接口的關系 圖:

圖2 ReversibleSortedList類接口關系圖

其實 ReversibleSortedList類的大部份代碼都用於實現左邊這兩個接口,看到這張圖 你應該理解了為什麼會需要1300行的代碼。為了一步一步地實現這個類,我們需 要首先實現底層接口,由於ICollection接口本身就繼承自IEnumerable接口,所 以首先應該實現的是IEnumerable接口。從前面一節可以知道IEnumerable接口的 實現需要一個嵌套類,它返回一個枚舉器。我們可以為IDictionary<TKey, TValue>接口和IDictionary接口實現同一個枚舉器,只要這個枚舉器實現了 IEnumerator<KeyValuePair<K, V>>接口和IDictionaryEnumerator 接口就可以了。前者是IDictionary<TKey, TValue>接口所需要的枚舉器 ,後者是IDictionary接口所需要的枚舉器(見前一節)。下面是我們的這個枚 舉器所需實現的接口的關系圖:

圖3 Enumerator<K, V>類接口關系圖

實現這個類需要注 意一個問題,在ReversibleSortedList類中添加了一個新的成員變量:version 。在插入元素時(insert方法)會更改這個version值,表示類中的元素已經進 行了改動。為什麼要使用它呢?在此我做些推測。從MSDN中或許可以找到線索。 我們在MSDN找到關於Dictionary<TKey, TValue>類的介紹,這個類跟 ReversibleSortedList類非常相似。在關於它的線程安全中有這麼一段話:

此類型的公共靜態成員是線程安全的。但不能保證任何實例成員是線程 安全的。

只要不修改該集合,Dictionary<(Of <(TKey, TValue>)>) 就可以同時支持多個閱讀器。 即便如此,從頭到尾對一個集 合進行枚舉本質上並不是一個線程安全的過程。當出現枚舉與寫訪問互相爭用這 種極少發生的情況時,必須在整個枚舉過程中鎖定集合。若要允許多個線程訪問 集合以進行讀寫操作,則必須實現自己的同步。

可以推測,version用於 在枚舉過程中判斷是否有其他線程更改了ReversibleSortedList實例中存儲的元 素,以便彈出異常。這一點可以在稍後的代碼中看見。

這時可能有人會 問了,前面一節中關沒有這種判斷啊?好,讓我們看看上一節關於IDictionary 接口的代碼。在它的實現枚舉器的嵌套類SimpleDictionaryEnumerator中,看看 它的構造方法:

public SimpleDictionaryEnumerator (SimpleDictionary sd)
    {
      items = new DictionaryEntry[sd.Count];
      Array.Copy(sd.items, 0, items, 0, sd.Count);
    }

從代碼中可以看出,它 把外部類中的所有元素拷貝到另一塊內存中進行枚舉,這樣在多線程訪問集合時 自然不會出錯,但如果集合中的元素很多就會帶來性能上的損失。而我們實現的 ReversibleSortedList類將直接使用集合中的元素進行枚舉,所以需要使用 version來保證在出錯時可以彈出異常。

下面我們在 “ReversibleSortedList 0.3版本”的基礎上繼續構建。關於 version的代碼這裡不再講解,請大家查看稍後完整的0.4版本的代碼。首先添加 一個實現枚舉器的嵌套類:

private struct Enumerator<K, V> : IEnumerator<KeyValuePair<K, V>>, IDisposable,
    IDictionaryEnumerator, IEnumerator
  {
     private ReversibleSortedList<K, V> _ReversibleSortedList;
    private K key;
    private V value;
     private int index;
    private int version;
     internal Enumerator(ReversibleSortedList<K, V> ReversibleSortedList)
    {  //獲取外部類中的元素引用,以對 它進行枚舉
      this._ReversibleSortedList = ReversibleSortedList;
      this.index = 0;
       this.version = this._ReversibleSortedList.version;//記錄外部類版本號
      //設置鍵和值為其默認類型,請參考:
       //http://cgbluesky.blog.163.com/blog/static/2412355820081695340822/
      this.key = default(K);
      this.value = default(V);
    }
    public void Dispose()
     {
      this.index = 0;
      this.key = default(K);
      this.value = default(V);
    }
    object IDictionaryEnumerator.Key
    {
       get
      {
        if ((this.index == 0) ||
          (this.index == (this._ReversibleSortedList.Count + 1)))
        {
          throw new InvalidOperationException(
               "不能進行枚舉操作.");
         }
        return this.key;
      }
     }
    public bool MoveNext()
    {
       if (this.version != this._ReversibleSortedList.version)
       {  //版本檢查
        throw new InvalidOperationException("枚舉版本檢查失敗!");
       }
      if (this.index < this._ReversibleSortedList.Count)
      {
         this.key = this._ReversibleSortedList.keys[this.index];
         this.value = this._ReversibleSortedList.values [this.index];
        this.index++;
         return true;
      }
      this.index = this._ReversibleSortedList.Count + 1;
      this.key = default(K);
      this.value = default(V);
       return false;
    }
    DictionaryEntry IDictionaryEnumerator.Entry
    {
      get
      {
        if ((this.index == 0) ||
           (this.index == (this._ReversibleSortedList.Count + 1)))
        {
          throw new InvalidOperationException("不能進行枚舉操作.");
         }
        return new DictionaryEntry(this.key, this.value);
      }
    }
    public KeyValuePair<K, V> Current
    {
       get
      {
        return new KeyValuePair<K, V>(this.key, this.value);
      }
    }
    object IEnumerator.Current
    {
      get
      {
        if ((this.index == 0) ||
          (this.index == (this._ReversibleSortedList.Count + 1)))
        {
          throw new InvalidOperationException("不能進行 枚舉操作");
        }
        return new DictionaryEntry(this.key, this.value);
      }
     }
    object IDictionaryEnumerator.Value
     {
      get
      {
        if ((this.index == 0) ||
          (this.index == (this._ReversibleSortedList.Count + 1)))
        {
          throw new InvalidOperationException("不能進行 枚舉操作");
        }
        return this.value;
      }
    }
    void IEnumerator.Reset()
    {
      if (this.version != this._ReversibleSortedList.version)
      {
         throw new InvalidOperationException("枚舉版本檢查失敗! ");
      }
      this.index = 0;
       this.key = default(K);
      this.value = default (V);
    }
  }

這個嵌套類的代碼對照圖3很 容易看懂,每個方法的功能在MSDN中也有詳細的介紹,這裡不再對它進行講解。 接下來要給外部類實現IEnumerable<KeyValuePair<TKey, TValue>>和 IEnumerable接口。更改類聲明代碼如下:

public class ReversibleSortedList<TKey, TValue> :

IEnumerable<KeyValuePair<TKey, TValue>>, IEnumerable

當然,這兩個接口分別只有一個成員:GetEnumerator()方 法,剛才所創建的嵌套類就是為這個方法所創建的。接下來在 ReversibleSortedList類中使用顯式接口成員實現來實現這兩個接口:

IEnumerator<KeyValuePair<TKey, TValue>>
     IEnumerable<KeyValuePair<TKey, TValue>>.GetEnumerator()
  {
    return new Enumerator<TKey, TValue>(this);
  }
  IEnumerator IEnumerable.GetEnumerator()
  {
    return new Enumerator<TKey, TValue>(this);
  }

好,現在 更改Main()方法中的代碼看看是否可以使用foreach循環來訪問 ReversibleSortedList中的元素,當然,前面所寫的Print()成員方法可以退休 了。

static void Main()
  {
     ReversibleSortedList<int, string> rs=new ReversibleSortedList<int, string>();
    rs.Add (3,"a");
    rs.Add(1,"b");
     rs.Add(2,"c");
    rs.Add(6,"d");
     rs.Add(5,"e");
    rs.Add (4,"f");
    foreach (KeyValuePair<int, string> d in rs)
    {
      Console.WriteLine (d.Key + "  " + d.Value);
    }
  }

由於代碼已經達到300行,貼到博客上會導致運行緩慢,後面所 有的可運行代碼將以文件的形式給出,大家可以直接下載運行。

ReversibleSortedList 0.4版本:實現迭代

運行結果:

1  b
2  c
3  a
4  f
5  e
6  d

真棒,現在已經取得了階段性的成果。但還有一些遺憾 ,雖然在Enumerator類中實現了IDictionaryEnumerator接口,但還不能在 foreach中使用DictionaryEntry訪問元素。這是因為IDictionary接口重載了 GetEnumerator()接口,而它返回的是一個IDictionaryEnumerator接口,也就是 說,只有實現了IDictionary接口才能使用DictionaryEntry訪問其中元素。實現 IDictionary接口之前我們需要建立一些輔助的內部類,這將在下一節進行講解 。

本文配套源碼

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved