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