• 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#最新版本的一些增强特性。越早将非泛型代码切换到泛型版本,你就能够越早的享受到它带来的便利。

        
    作者:李潘
             
    本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。
  • 相关阅读:
    设置文本框的九种对齐方式(左上,中上,右上,左中,中中,右中,左下,中下,右下)
    VB实现小数和分数的相互转化
    已知三角形三个边的长度值,求三个角的大小
    全国专业技术人员计算机应用能力考试
    EXCELSHEET 中"输入”或“编辑”状态与“就绪”状态的切换
    递归方法巧解不定方程(二)
    VB计算圆周率
    获取路径名的原始大小写状态
    opengl NeNe 第二课的学习
    广东电信公话业务中CRM系统的研究与探索
  • 原文地址:https://www.cnblogs.com/wing011203/p/1686729.html
Copyright © 2020-2023  润新知