標籤:
一、前言
首先談談泛型,包括Java, C++都有自己的泛型(模版),這種機制大大的減少了代碼的數量,是一種類型的抽象。集合就我瞭解C++的 STL 中的vector<T>, list<T>, map<T,T> 等, .net 中的List<T>, HashTable<T,T>等,都是對基本資料結構的實現,如鏈表,隊列,棧,等。但是在具體使用中,不同的語言,如果使用不當,我造成嚴重的效能影響,合格的程式員應該瞭解這些效能陷阱。
二、.net 泛型
在.net中通過使用泛型,我們可以達到以下兩個目的:
1.Type safe 2. No Boxing.
這個比較好理解,舉個例子ArrayList, 其源碼如下:
public class ArrayList : IEnumerable, ICollection, IList { private object[] items; private int size; public ArrayList(int initalCapacity) { items = new object[initalCapacity]; } public void Add(object item) { if (size < items.Length - 1) { items[size] = item; ++size; } else { //Allocate a larger array, copy the elements to there. } } public object this[int index] { get { if (index < 0 || index >= size) throw new IndexOutOfRangeException(); return items[index]; } set { if (index < 0 || index >= size) throw new IndexOutOfRangeException(); } } // ommit other details }
可見在Add的時候會有裝箱操作發生,如果存放的是1,000,000的Point, 將會有大量的記憶體被浪費掉(8M (extra)+ 8M(data) + 4M(reference)), 除了因為裝箱引起記憶體浪費外,因為我們相關的操作時基於System.Object,型別安全也是一個大問題。
泛型可以完美的解決這個問題,原理看簡化的源碼:
public class List<T> : IEnumerable<T>, ICollection<T>, IList<T> { private T[] items; private int size; public List(int initalCapacity) { // does this work? items = new T[initalCapacity]; } public void Add(T item) { if (size < items.Length - 1) { items[size] = item; ++size; } else { //Allocate a larger array, copy the elements to there. } } public T this[int index] { get { if (index < 0 || index >= size) throw new IndexOutOfRangeException(); return items[index]; } set { if (index < 0 || index >= size) throw new IndexOutOfRangeException(); items[index] = value; } } // ommit other details }
不用擔心型別安全和裝箱拆箱的問題了。但是如果我增加一些比較的功能呢?
public static int BinarySearch<T>(T[] array, T element) {
//At some point in the algorithm, we need to compare:
if (array[x] < array[y]) {
...
}
}
System.Object沒有實現 static operator <, 對於上面這個函數,大部分都會類型都會編譯錯誤。我們可以使用模版函數的限制功能,來保證T實現了 比較的 , .NET一種有5種限制:
public class Widget{ public void Display(int i, int j) { } } public class GenericDemo { // T must implement an interface public string Format<T>(T instance) where T: IFormattable { return instance.ToString("N", CultureInfo.CurrentCulture); // OK, T must have IFormattable.ToString(...) } // T must based on a base class public void Display<T>(T widget) where T : Widget { widget.Display(1, 2); } // T must a parameterless cosntructor public T Create<T>() where T : new() { return new T(); } // T must be a reference type: public void ReferencesOnly<T>(T reference) where T : class { } // T must be a value type: public void ValueType<T>(T valueType) where T : struct { } }
這樣我們可以這樣寫BinarySearch了:
public static int BinarySearch<T>(T[] array, T element) where T : IComparable<T> {
//At some point in the algorithm, we need to compare:
int x = 1; int y = 2;
if (array[x].CompareTo(array[y]) < 0) {
//...
}
}
接下來我們再討論 IEquatable<T>,先看下面這個函數:
public static void CallEquals<T>(T instance) {instance.Equals(instance);}
Equals將會調用基類的虛函數Equals,它的參數是System.Object,會產生裝箱。但是我們實現了IEquatable<T>,使用在模版限定中, 就可以避免裝箱了
//From the .NET Framework:public interface IEquatable<T> {bool Equals(T other);}public static void CallEquals<T>(T instance) where T : IEquatable<T> {instance.Equals(instance);}
這個函數將不再調用虛函數的Equals, 這樣就避免了裝箱。我們在前一篇文章中,提到valueType的最佳實務中,要現實IEquatable<T> 就是這個原因。 那麼按理說所有的集合最好都限制為IEquatable<T>類型,但是為了擴充性的考慮,我們用組合的形式,委託給GenericEqualityComparer, 舉個例子List<T>.Contains, 前看簡化源碼:
public bool Contains(T item) { if (item == null) { for (int i = 0; i < this._size; i++) { if (this._items[i] == null) { return true; } } return false; } EqualityComparer<T> @default = EqualityComparer<T>.Default; for (int j = 0; j < this._size; j++) { if (@default.Equals(this._items[j], item)) { return true; } } return false; }
把比較委託給了EqualityComaprer<T>, 如果我們替換了它,就可以改變我們的比較策略了。除了equals,這裡還有別的實現數學的泛型知識。請看參考連結
三、參考
<<Pro .NET Performance>>
http://www.codeproject.com/Articles/8531/Using-generics-for-calculations
C# 泛型