More Effective C# Item1 : 使用1.x框架API的泛型版本

    .NET平台前两个版本不支持泛型,因此,在程序中只能基于System.Object进行编码,然后通过必要的运行时检查保证程序的正确性。

    这样做是没有错的,毕竟System.Object是所有类型的基类,在程序中可以在任何时候任何地方使用System.Object传递参数,但是我们在程序中,一般会调用某些指定类型的方法,而这些方法并不会定义在System.Object中,这也就是说我们要对类型进行转换,将Object转换成某种我们期望的类型,然后才可以调用期望类型中的方法。

    在引入泛型后,之所以不再推荐上述的做法,有以下两个原因:

  1. 程序的健壮性和可维护性。如果大部分的方法参数传递的都是System.Object,那么不可避免要进行类型转换,如果有类型转换,因为类型转换是否正确,并不会编译时进行检查,那么我们必须在代码中添加大量对转换异常的处理,或者说对运行时对象类型的检查。这样的代码,一方面在很多时候都是重复的,另一方面很容易有遗漏,造成程序运行时错误。
  2. 程序的性能。在类型转换的过程中,不可避免会有拆箱和装箱的过程,如果进行类型转换的很频繁,那么会对程序的性能造成很大的影响。

    在.NET 2.0引入泛型后,上述两个问题在一定程度上得到了解决,使用泛型,可以在编译时对类型进行检查,如果传递的参数不符合泛型的约束,那么编译就不会通过,这样可以提前发现问题;另一方面,使用泛型,没有了装箱和拆箱的操作,对提高程序的性能也有一定的帮助。

    如果想学习泛型,那么从对泛型“传统”的认识开始是一个不错的选择,所谓“传统”,就是指集合类。泛型在涉及到集合的存储和操作过程中,有很大的发挥空间,为此,.NET框架专门建立了一个新的命名空间,即System.Collections.Generics。

    我们首先来看一下之前弱类型框架中的情况,即System.Collections,为了保证兼容性,.NET 2.0保留了之前非泛型的类型说明。

    System.Collections命名空间中的类说明如下。

  •   ArrayList  使用大小可按需动态增加的数组实现 IList 接口。
  •  BitArray  管理位值的压缩数组,该值表示为布尔值,其中 true 表示位是打开的 (1),false 表示位是关闭的 (0)。
  •  CaseInsensitiveComparer  比较两个对象是否相等,比较时忽略字符串的大小写。
  •  CaseInsensitiveHashCodeProvider  已过时。使用忽略字符串大小写的哈希算法,为对象提供哈希代码。
  •  CollectionBase  为强类型集合提供 abstract 基类。
  •  Comparer  比较两个对象是否相等,其中字符串比较是区分大小写的。
  •  DictionaryBase  为键/值对的强类型集合提供 abstract 基类。
  •  Hashtable  表示键/值对的集合,这些键/值对根据键的哈希代码进行组织。
  •  Queue  表示对象的先进先出集合。
  •  ReadOnlyCollectionBase  为强类型非泛型只读集合提供 abstract 基类。
  •  SortedList  表示键/值对的集合,这些键值对按键排序并可按照键和索引访问。
  •  Stack  表示对象的简单的后进先出非泛型集合。

    System.Collections命名空间中的接口说明如下。

  •  ICollection  定义所有非泛型集合的大小、枚举数和同步方法。
  •  IComparer  公开一种比较两个对象的方法。
  •  IDictionary  表示键/值对的非通用集合。
  •  IDictionaryEnumerator  枚举非泛型字典的元素。
  •  IEnumerable  公开枚举数,该枚举数支持在非泛型集合上进行简单迭代。
  •  IEnumerator  支持对非泛型集合的简单迭代。
  •  IEqualityComparer  定义方法以支持对象的相等比较。
  •  IHashCodeProvider  已过时。使用自定义哈希函数为对象提供哈希代码。
  •  IList  表示可按照索引单独访问的对象的非泛型集合。

    .NET 2.0中,在System.Collections.Generics命名空间中,针对上述类和接口,基本上都提供了泛型的版本,我们来看一下。

    System.Collections.Generics命名空间中的类说明如下。

    System.Collections.Generics命名空间中的接口说明如下。

    上述类和接口的说明都来自MSDN,可以点击进入每一个类型中进行详细查看。

    接下来,我们看一下System命名空间新添加的一个接口,IEqutable<T>,该接口的声明如下。

1 public interface IEquatable<T>
2 {
3 bool Equals(T other);
4 }

    如果我们需要在定义类型时,重写Object的Equals方法,那么最好事先这个接口,来看以下的代码。

代码
1 internal class Employee : IEquatable<Employee>
2 {
3 private string m_strEmpName;
4 internal string EmpName
5 {
6 get { return m_strEmpName; }
7 set { m_strEmpName = value; }
8 }
9
10 private string m_strAddress;
11 public string Address
12 {
13 get { return m_strAddress; }
14 set { m_strAddress = value; }
15 }
16
17 /// <summary>
18 /// IEqutable interface
19 /// </summary>
20 /// <param name="other"></param>
21 /// <returns></returns>
22 public bool Equals(Employee other)
23 {
24 return this.EmpName.Equals(other.EmpName) && this.Address.Equals(other.Address);
25 }
26
27 /// <summary>
28 /// override object's equals method.
29 /// </summary>
30 /// <param name="obj"></param>
31 /// <returns></returns>
32 public override bool Equals(object obj)
33 {
34 if (obj == null)
35 {
36 return false;
37 }
38 if (object.ReferenceEquals(this, obj))
39 {
40 return true;
41 }
42 if (this.GetType() != obj.GetType())
43 {
44 return false;
45 }
46 return Equals(obj as Employee);
47 }
48 }

    上述代码中,如果我们在调用Equals方法时,传入的参数类型是Employee,那么会非常简化执行的过程。

    如果我们需要对定义在其他类库中的类型进行比较,那么我们还可以使用System.Collections.Generics命名空间中的IEqulityComparer<T>接口,该接口的声明如下。

1 public interface IEqualityComparer<T>
2 {
3 int Equals( T x, T y);
4 int GetHashCode(T obj);
5 }

    一般情况下,我们不需要直接实现IEqulityComparer<T>接口,而是使用EqulityComparer<T>类的Default属性,但是在有些情况下,还是需要实现该接口(因为.NET是单继承,有时不应该把这一次宝贵的继承机会放在这里)。

    来看下面的代码,演示了如何使用Default属性。

代码
1 internal class EmployeeComparer : EqualityComparer<Employee>
2 {
3 public override bool Equals(Employee x, Employee y)
4 {
5 return EqualityComparer<Employee>.Default.Equals(x, y);
6 }
7
8 public override int GetHashCode(Employee obj)
9 {
10 return EqualityComparer<Employee>.Default.GetHashCode(obj);
11 }
12 }

    上述示例告诉我们,越是基础的算法,可能越需要一个泛型类型定义,例如Equals方法,利用编译时类型检查,我们可以大大减少代码量,并可以提高程序的性能。

    下面我们来看一下System.Collections.Generics命名空间中的IComparable<T>接口,该接口的声明如下。

1 public interface IComparable<T>
2 {
3 int CompareTo(T other);
4 }

    和旧版本IComparable接口相比,新接口中直接将参数类型进行了转换,我们来看下面的代码。

代码
1 #region IComparable Members
2
3 public int CompareTo(object obj)
4 {
5 if (!(obj is Employee))
6 {
7 throw new ArgumentException("obj is not Employee!");
8 }
9 Employee temp = obj as Employee;
10 return this.EmpName.CompareTo(temp.EmpName);
11 }
12
13 #endregion
14
15 #region IComparable<Employee> Members
16
17 public int CompareTo(Employee other)
18 {
19 return this.EmpName.CompareTo(other.EmpName);
20 }
21
22 #endregion

    上述代码让Employee类分别实现了IComparable接口和IComparable<T>接口,可以看出,带有泛型的接口,非常简洁,并且不会抛出任何运行时异常。

    在引入了泛型后,并不是之前非泛型的接口就没有用武之地了,有些情况下,还是需要使用非泛型的版本,例如当我们在进行比较时,如果是我们创建的类型之间进行比较,那么可以使用泛型版本的比较方式,但是如果需要使用我们创建的类型和第三方提供的类型进行比较,那么这时,就只能使用非泛型版本的比较方式了。

    .NET框架同时也添加了一些泛型的委托,用来实现一些常见的模式。

    例如我们向Employee类中添加一个名为OutputInfo的方法,用于输出员工信息,然后针对一个Employee类型的List,需要输出List中所有元素的信息。

    首先来看第一种实现方式。

1 private static void TestEnum(List<Employee> lstEmp)
2 {
3 foreach (Employee emp in lstEmp)
4 {
5 emp.OutputInfo();
6 }
7 }

    然后看一下如何使用泛型方式来实现。

1 private static void TestEnumWithDelegate<T>(List<T> lstEmp, Action<T> doIt)
2 {
3 foreach (T temp in lstEmp)
4 {
5 doIt(temp);
6 }
7 }
8
9 private static void Output(Employee emp)
10 {
11 emp.OutputInfo();
12 }

    上述代码编写了两个方法,我们来看一下上述两种方式应该如何调用,如下。

 

1 List<Employee> lstEmp = new List<Employee>();
2 lstEmp.Add(new Employee("Wing", "BeiJing"));
3 lstEmp.Add(new Employee("UnKnown", "Moon"));
4 TestEnum(lstEmp);
5 TestEnumWithDelegate<Employee>(lstEmp, Output);

    当我们需要对List<T>进行排序时,我们可以指定排序的方式,List<T>的Sort方法添加了一个重载的形式,它以Comparsion<T>类型的代理作为参数,我们来看下面的代码。

 

1 private static void SortList(List<Employee> lstEmp)
2 {
3 lstEmp.Sort( delegate(Employee x, Employee y){return x.EmpName.CompareTo(y.EmpName);});
4 }

    上述代码就是通过使用匿名代理的方式,实现了List排序的方式。

 

 

    总结:通过上面的描述,我们可以看到泛型给我们带来了很多的便利,它也会让我们改变很多之前的编程习惯。通过泛型,你可以使得组件更加容易使用,同时还可以避免很多错误,很多类型检查可以放在编译时进行,这样可以使得代码非常清晰。而使用内建的泛型委托定义,我们可以使用C#最新版本的一些增强特性。越早将非泛型代码切换到泛型版本,你就能够越早的享受到它带来的便利。

posted @ 2010-03-15 22:57  李潘  阅读(695)  评论(0编辑  收藏  举报