泛型和反射
1.1.1 摘要
在前一博文《.NET 中的泛型 101》中我們介紹了泛型的基本用法,現在我們繼續介紹泛型的進階用法(如:泛型的比較接口、迭代實現、泛型類型和方法的反射)。
泛型的比較接口提供了實現對象比較和排序。
由于公共語言運行庫 (CLR) 能夠在運行時(Run time)訪問泛型類型信息,所以可以使用反射獲取關于泛型類型的信息,方法與用于非泛型類型的方法相同。
在.NET Framework 1.0中,我們可以使用Type.GetType()獲取Type類型的對象,當然1.0時,還沒有引入泛型。
在.NET Framework 2.0,Type類增添了幾個新成員以獲取泛型類型的運行時(Run time)信息。
本文目錄
1.1.2 正文
泛型比較接口
泛型的四種比較接口:IComparer<T>、IComparable<T>、IEqualityComparer<T>和IEquatable<T>。
其中,IComparer<T>和IComparable<T>實現比較排序,而IEqualityComparer<T>和IEquatable<T>實現條件比較并且獲取相應元素的哈希值。
IComparer<T>和IEqualityComparer<T>可以實現不同類型之間的比較,而IComparable<T>和IEquatable<T>只能在同一類型中進行比較。
假設,我們定義了一個學生類,它用來記錄學生的基本信息(如:Id、FirstName、LastName和Tel等),具體定義如下:
/// <summary> /// The student model. /// </summary> public class Student { public long Id { get; set; } public string FirstName { get; set; } public string LastName { get; set; } public long Tel { get; set; } }
現在,我們創建一個Student類型的List。
// Creates student object with initializer. var students = new List<Student>() { new Student() { Id = 245712348, FirstName = "Ann", LastName = "Chen", Tel = "18022007281" }, new Student() { Id = 245712345, FirstName = "Ada", LastName = "Cao", Tel = "18022007281" }, new Student() { Id = 245712347, FirstName = "Rush", LastName = "Huang", Tel = "18022007281" }, new Student() { Id = 245712346, FirstName = "Jackson", LastName = "Huang", Tel = "18022007281" }, new Student() { Id = 245712349, FirstName = "Maggie", LastName = "Yip", Tel = "18022007281" }, };
上面,我們在List中創建了五個學生對象,如果我們要根據學號(Id)對學生對象進行排序,這時可以通過實現IComparable<T>接口實現對象排序。
/// <summary> /// The student model. /// </summary> public class Student : IComparable<Student> { public long Id { get; set; } public string FirstName { get; set; } public string LastName { get; set; } public string Tel { get; set; } #region Implementation of IComparable<Student> public int CompareTo(Student other) { if (this.Id > other.Id) { return 1; } } #endregion }
我們通過實現IComparable<T>接口,讓List根據Id進行排序,我們需要實現CompareTo()方法根據Id排序。
現在,我們又有一個疑問:如果Student類不僅僅根據Id排序,還希望根據LastName或FirstName排序,這時,我們可以給CompareTo()方法增加LastName或FirstName排序條件就OK了。
/// <summary> /// The student model. /// </summary> public class Student : IComparable<Student> { public long Id { get; set; } public string FirstName { get; set; } public string LastName { get; set; } public string Tel { get; set; } #region Implementation of IComparable<Student> public int CompareTo(Student other) { if (this.Id > other.Id) { return 1; } // If the id is the same, then comparing lastname and first name. return string.Compare( this.LastName, other.LastName) != 0 ? string.Compare(this.LastName, other.LastName) : string.Compare(this.FirstName, other.FirstName); } #endregion }
假設,需求變得更加糟糕排序條件不固定,有可能根據Id排序,也有可能根據LastName或FirstName排序。
這時,我們可以通過自定義類并且實現IComparer<T>接口,讓List根據自定義排序條件排序,而且需要實現compare()方法;它包含兩個對象的參數分別是x和y,如果x小于y返回一個負值,相等返回零,x大于y返回一個正數。
接下來,讓我們定義排序條件根據學生的姓名進行排序,具體實現如下:
/// <summary> /// The student comparer. /// </summary> public class StudnetComparer : IComparer<Student> { #region Implementation of IComparer<Student> // Compares the lastname of students. public int Compare(Student x, Student y) { return string.Compare(x.LastName, y.LastName); } #endregion }
上面,我們定義排序類StudnetComparer,它實現了IComparer<T>接口并且添加了排序條件,讓StudnetComparer根據學生的姓名進行排序。
假設我們想根據Id排序,那么我們可以擴展StudnetComparer的Compare()方法,當讓我們也可以定義一個新的比較類。
圖1排序結果
泛型迭代
迭代是集合中最常用的操作之一,通過迭代可以訪問集合中的元素,相信大家都使用foreach語句來訪問集合中的元素,它就是通過迭代的方式去訪問集合中的元素。
在C# 1.0中,迭代訪問集合需要實現System.Collections.IEnumerable接口或有一個GetEnumerator()方法返回一個合適類型對象,通過該對象的MoveNext()方法和Current屬性訪問集合中的元素。
接下來,我們通過foreach語句迭代訪問ArrayList集合中的元素,對于大家來說這再簡單不過了,示例如下:
foreach (var number in numberArray) { Console.WriteLine(string.Format("number: {0}", number)); }
我們知道只有類型實現了IEnumerable接口或有一個GetEnumerator()方法才可以通過foreach語句迭代訪問,現在我們就有一個疑問foreach語句具體進行了哪些操作呢?
其實,foreach語句簡化了整個迭代訪問的過程,接下來我們將給出foreach語句的具體實現。
ArrayList numberArray = new ArrayList() { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }; IEnumerator enumerator = numberArray.GetEnumerator(); while (enumerator.MoveNext()) { // Inboxing operation. Object number = enumerator.Current; Console.WriteLine(string.Format("number: {0}", number)); }
首先,numberArray調用GetEnumerator()方法獲取一個IEnumerator對象,接著遍歷訪問enumerator中的元素,這里我們要注意enumerator.Current獲取的是ArrayList中的元素,由于集合中的元素是值類型,所以轉換為引用類型(Object)需要進行裝箱操作。
圖2排序結果
在C# 2.0中,由于引入了泛型并且使用泛型接口IEnumerable<T>擴展了IEnumerable接口,那么foreach語句將可以使用IEnumerable或IEnumerable<T>接口訪問集合中的元素。
圖3 IEnumerable<T>接口
在前面給出的例子中,如果訪問值類型集合時,那么值類型元素需要進行裝箱操作;當訪問泛型集合時(如:List<int>),那么值類型元素是否需要裝箱操作呢?
IEnumerator<int> enumerator = numberArray.GetEnumerator(); while (enumerator.MoveNext()) { int number = enumerator.Current; Console.WriteLine(string.Format("number: {0}", number)); ////string.Format( ////"Id:{0} FirstName:{1} LastName:{2} Tel:{3}", ////student.Id, student.FirstName, student.LastName, student.Tel)); } var disposable = enumerator as IDisposable; disposable.Dispose();
上面,我們給出了值類型泛型集合的訪問實現,我們發現值類型元素無需轉換為引用類型,所以無需進行裝箱操作。
接下來,我們將通過foreach語句迭代訪問引用類型集合studnets中的元素,具體實現如下:
foreach (var student in students) { Console.WriteLine( string.Format( "Id:{0} FirstName:{1} LastName:{2} Tel:{3}", student.Id, student.FirstName, student.LastName, student.Tel)); }
其實,foreach語句背后的操作,首先,調用了students集合的GetEnumerator()方法,然后,通過MoveNext()方法遍歷集合中的元素,接著通過Current屬性獲取當前元素對象。
IEnumerator enumerator = students.GetEnumerator(); while (enumerator.MoveNext()) { Student student = enumerator.Current as Student; Console.WriteLine( string.Format( "Id:{0} FirstName:{1} LastName:{2} Tel:{3}", student.Id, student.FirstName, student.LastName, student.Tel)); } var disposable = enumerator as IDisposable; disposable.Dispose();
泛型類型的反射
反射提供了封裝程序集、模塊和類型的對象(Type 類型),我們可以通過反射動態創建類型的實例,將類型綁定到現有對象,或從現有對象獲取類型并調用其方法或訪問其字段和屬性。如果代碼中使用了屬性,可以利用反射對它們進行訪問。
C#使用typeof()方法來獲的編譯時類型。
在泛型中,typeof()方法有兩種用法,一種用來獲取“開放”泛型(Generic type)的Type對象,另一種是獲取“封閉”泛型即構造泛型(Constructed type)的Type對象。
現在,我們有一個疑問什么是“開放”泛型,什么是“封閉”泛型呢?
其實,理解所有這一切的關鍵是理解兩種不同的Type對象和泛型類之間的關系,假設我們定義了一個泛型類,然后,給它添加一個或多個類型不確定的成員,直到真正調用它的時候類型才確定下來,這就是所謂的“開放”泛型;當我們聲明了一個“開放”泛型的引用并且提供具體的成員類型,這就是所謂的“封閉”泛型即構造泛型。
接下來,我們將介紹通過typeof()方法獲取公開和封閉泛型的Type對象。
Console.WriteLine(typeof(string)); // Open type. Console.WriteLine(typeof(List<>)); // If have mutiple parameter, should keep commas. Console.WriteLine(typeof(Dictionary<,>)); // Constructed type. Console.WriteLine(typeof(List<string>)); Console.WriteLine(typeof(Dictionary<string, long>)); Console.WriteLine(typeof(List<long>)); Console.WriteLine(typeof(Dictionary<long, Guid>));
圖4 typeof方法獲取Type對象
上面,我們通過typeof()方法獲取“開放”和“封閉”泛型的Type對象。
其中有兩點是我們要注意的,首先輸出結果中包含的數字如:‘1或‘2表示參數個數,而且,我們發現對于“開放”泛型參數類型都是不確定,它們使用了如:T或TValue占位符,而“封閉”泛型參數類型都確定的。
前面,我們使用typeof()方法獲取泛型類型的Type對象,其實,我們還可以使用Type類的成員方法獲取泛型類型的Type對象,它們分別是 GetGenericTypeDefinition()和MakeGenericType()。
GetGenericTypeDefinition()方法獲取一個表示可用于構造當前泛型類型的泛型類型定義的 Type對象,MakeGenericType()方法替代由當前泛型類型定義的類型參數組成的類型數組的元素,并返回表示結果構造類型的 Type 對象。
這兩個方法的描述十分繞口,簡而言之,我們通過GetGenericTypeDefinition()方法獲取“封閉”泛型的泛型定義,而MakeGenericType()方法根據泛型定義獲取“封閉”類型。
其實,在C# 1.0中也包含類似功能的方法Type.GetType()和Assembly.GetType()方法。
接下來,我們將使用Type的成員方法獲取泛型類型的Type對象。
string listTypeName = "System.Collections.Generic.List`1"; Type defByName = Type.GetType(listTypeName); // Retrieves the type of List<string> through the approach as below. Type closedByName = Type.GetType(listTypeName + "[System.String]"); Type closedByMethod = defByName.MakeGenericType(typeof(string)); Type closedByTypeof = typeof(List<string>); Console.WriteLine(closedByMethod == closedByName); Console.WriteLine(closedByName == closedByTypeof); // Retrieves open type object through the approach as below. Type defByTypeof = typeof(List<>); Type defByMethod = closedByName.GetGenericTypeDefinition(); Console.WriteLine(defByMethod == defByName); Console.WriteLine(defByName == defByTypeof); Console.ReadKey(); // OUTPUT: // True // True // True // True
上面的輸出結果都為True,但我們注意到defByMethod、defByName、defByName和defByTypeof都是特定類型Type對象,而我們使用了“==”判斷兩個Type對象是否相同,也就是說,對于同一泛型類型分別通過typeof()或GetType()方法得到的是同一個Type對象引用。
泛型方法的反射
前面,我們介紹了使用typeof()方法或Type的成員方法獲取泛型類型的Type對象,至于泛型方法的反射,我們使用的是MethodInfo類的成員方法MakeGenericMethod()。
/// <summary> /// Defines a generic class. /// </summary> /// <typeparam name="T">T can be value or reference type.</typeparam> public class GenericClass<T> { private T _t = default(T); public GenericClass(T t) { _t = t; Console.WriteLine("GenericClass<{0}>( {1} ) object created", typeof(T).FullName, _t.ToString()); } public T GetValue() { Console.WriteLine("GetValue() method invoked, returning {0}", _t.ToString()); return _t; } public static U StaticGetValue<U>(U u) { Console.WriteLine("StaticGetValue<{0}>( {1} ) method invoked", typeof(U).FullName, u.ToString()); return u; } }
上面,我們定義了泛型類GenericClass<T>,接下來,我們將使用MakeGenericMethod()方法獲取泛型方法的MethodInfo對象,接著通過Invoke()方法調用該泛型方法。
// Invokes the static template method directly GenericClass<int>.StaticGetValue(23); // Gets the open generic method type // Notes, we should specify the class type T first. MethodInfo openGenericMethod = typeof(GenericClass<string>).GetMethod("StaticGetValue"); // Gets the close generic method type, by supplying the generic parameter type MethodInfo closedGenericMethod = openGenericMethod.MakeGenericMethod(typeof(int)); object o2 = closedGenericMethod.Invoke(null, new object[] { 20120929 }); Console.WriteLine("o2 = {0}", o2.ToString());
圖5 泛型方法的反射
首先,我們使用GetMethod()方法獲取開放的泛型方法,接著使用MakeGenericMethod()構造封閉的泛型方法,最后調用MethodInfo對象的Invoke()方法傳遞參數和調用泛型方法StaticGetValue<U>()。
1.1.3 總結
本文介紹泛型的進階使用方法,如:泛型接口、迭代的實現、泛型類型的反射和泛型方法的反射。
泛型接口實現對象比較和排序,如:實現IComparer<T>、IComparable<T>、IEqualityComparer<T>和IEquatable<T>接口。
對于迭代訪問集合中的元素,我們一般可以使用foreach語句實現,這里我們介紹了foreach語句的迭代訪問實現。
最后,通過介紹“開放”泛型類型、“封閉”泛型類型、“開放”泛型方法和“封閉”泛型方法;我們學會了如何根據Type對象獲取相應的泛型對象或方法,我們學習了如何使用MakeGenericType()方法構造“封閉”泛型類型,這樣我們就可以通過Activator.CreateInstance()方法實例化該類型;通過MakeGenericMethod()方法構造封閉”泛型方法,然后調用MethodInfo對象的Invoke()方法傳遞參數和調用泛型方法。
祝大家中秋和國慶節快樂,身體健康。
參考
[1] http://en.csharp-online.net/Generic_types
[2] http://www.codeproject.com/Articles/22088/Reflecting-on-Generics
[3] http://www.amazon.cn/C-in-Depth-Skeet-Jon/dp/1935182471/ref=sr_1_2?ie=UTF8&qid=1348972408&sr=8-2
|
|
關于作者:[作者]:
JK_Rush從事.NET開發和熱衷于開源高性能系統設計,通過博文交流和分享經驗,歡迎轉載,請保留原文地址,謝謝。 |

在前一博文《.NET 中的泛型 101》中我們介紹了泛型的基本用法,現在我們繼續介紹泛型的進階用法(如:泛型的比較接口、迭代實現、泛型類型和方法的反射)。...




浙公網安備 33010602011771號