泛型编程:C# 2.0中泛型编程初级入门教程

  在2005年底微软公司正式发布了C# 2.0和C# 1.x相比新版本增加了很多新特性其中最重要是对泛型支持通过泛型我们可以定义类型安全数据结构而无需使用实际数据类型这能显著提高性能并得到更高质量代码泛型并不是什么新鲜东西他在功能上类似于C模板模板多年前就已存在C上了并且在C上有大量成熟应用

  本文讨论泛型使用般问题比如为什么要使用泛型、泛型编写思路方法、泛型中数据类型约束、泛型中静态成员使用要注意问题、泛型中思路方法重载问、泛型思路方法等通过这些使我们可以大致了解泛型并掌握泛型般应用编写出更简单、通用、高效应用系统

  什么是泛型

  我们在编写经常遇到两个模块功能非常相似只是个是处理数据个是处理数据或者其他自定义数据类型但我们没有办法只能分别写多个思路方法处理每个数据类型思路方法参数类型区别有没有种办法在思路方法中传入通用数据类型这样不就可以合并代码了吗?泛型出现就是专门解决这个问题读完本篇文章你会对泛型有更深了解

  为什么要使用泛型

  为了了解这个问题我们先看下面代码代码省略了些内容但功能是实现个栈这个栈只能处理数据类型:

  public Stack
{
 private m_item;
 public Pop{...}
 public void Push( item){...}
 public Stack( i)
 {
  this.m_item = [i];
 }
}


  上面代码运行很好但是当我们需要个栈来保存类型时该如何办呢?很多人都会想到把上面代码复制改成不就行了当然这样做本身是没有任何问题个优秀是不会这样做他想到若以后再需要long、Node类型栈该怎样做呢?还要再复制吗?优秀员会想到用个通用数据类型object来实现这个栈:

  public Stack
{
 private object m_item;
 public object Pop{...}
 public void Push(object item){...}
 public Stack( i)
 {
  this.m_item = [i];
 }
}


  这个栈写不错他非常灵活可以接收任何数据类型可以说是劳永逸但全面地讲也不是没有缺陷主要表现在:

  当Stack处理值类型时会出现装箱、折箱操作这将在托管堆上分配和回收大量变量若数据量大则性能损失非常严重

  在处理引用类型时虽然没有装箱和折箱操作但将用到数据类型强制转换操作增加处理器负担

  在数据类型强制转换上还有更严重问题(假设stack是Stack个例子):

  Node1 x = Node1;
stack.Push(x);
Node2 y = (Node2)stack.Pop;


  上面代码在编译时是完全没问题但由于Push了个Node1类型数据但在Pop时却要求转换为Node2类型这将出现运行时类型转换异常但却逃离了编译器检查

  针对object类型栈问题我们引入泛型他可以优雅地解决这些问题泛型用用个通过数据类型T来代替object在类例子化时指定T类型运行时(Runtime)自动编译为本地代码运行效率和代码质量都有很大提高并且保证数据类型安全

  使用泛型

  下面是用泛型来重写上面个通用数据类型T来作为个占位符等待在例子化时用个实际类型来代替让我们来看看泛型威力:

  public Stack<T>
{
 private T m_item;
 public T Pop{...}
 public void Push(T item){...}
 public Stack( i)
 {
  this.m_item = T[i];
 }
}


  类写法不变只是引入了通用数据类型T就可以适用于任何数据类型并且类型安全这个类思路方法:

  //例子化只能保存类型
Stack<> a = Stack<>(100);
a.Push(10);
a.Push("8888"); //这行编译不通过类a只接收类型数据
x = a.Pop;
//例子化只能保存类型
Stack<> b = Stack<>(100);
b.Push(10); //这行编译不通过类b只接收类型数据
b.Push("8888");
y = b.Pop;


  这个类和object实现类有截然区别区别:

  1. 他是类型安全例子化了类型就不能处理类型数据其他数据类型也

  2. 无需装箱和折箱这个类在例子化时按照所传入数据类型生成本地代码本地代码数据类型已确定所以无需装箱和折箱

  3. 无需类型转换

  泛型类例子化理论

  C#泛型类在编译时先生成中间代码IL通用类型T只是个占位符在例子化类时根据用户指定数据类型代替T并由即时编译器(JIT)生成本地代码这个本地代码中已经使用了实际数据类型等同于用实际类型写所以区别封闭类本地代码是不按照这个原理我们可以这样认为:

  泛型类区别封闭类是分别区别数据类型

  例:Stack<>和Stack<>是两个完全没有任何关系你可以把他看成类A和类B这个解释对泛型类静态成员理解有很大帮助

  泛型类中数据类型约束

  员在编写泛型类时总是会对通用数据类型T进行有意或无意地有假想也就是说这个T般来说是不能适应所有类型但怎样限制者传入数据类型呢?这就需要对传入数据类型进行约束约束方式是指定T祖先即继承接口或类C#单根继承性所以约束可以有多个接口但最多只能有个类并且类必须在接口的前这时就用到了C#2.0新增关键字:

  public Node<T, V> where T : Stack, IComparable
where V: Stack
{...}


  以上泛型类约束表明T必须是从Stack和IComparable继承V必须是Stack或从Stack继承否则将无法通过编译器类型检查编译失败

  通用类型T没有特指C#中所有类都是从object继承来所以他在类Node编写中只能object类思路方法这给编写造成了困难比如你类设计只需要支持两种数据类型并且在类中需要对T类型变量比较大小但这些却无法实现object是没有比较大小思路方法 了解决这个问题只需对T进行IComparable约束这时在类Node里就可以对T例子执行CompareTo思路方法了这个问题可以扩展到其他用户自定义数据类型

  如果在类Node里需要对T重新进行例子化该如何办呢?类Node中不知道类T到底有哪些构造为了解决这个问题需要用到约束:

  public Node<T, V> where T : Stack,
where V: IComparable


  需要注意约束只能是无参数所以也要求相应类Stack必须有个无参构造否则编译失败

  C#中数据类型有两大类:引用类型和值类型引用类型如所有值类型般是语言最基本类型, long, struct等在泛型约束中我们也可以大范围地限制类型T必须是引用类型或必须是值类型分别对应关键字是和struct:

  public Node<T, V> where T :
where V: struct


  泛型思路方法

  泛型不仅能作用在类上也可单独用在类思路方法上他可根据思路方法参数类型自动适应各种参数这样思路方法叫泛型思路方法看下面类:

  public Stack2
{
 public void Push<T>(Stack<T> s, params T p)
 {
  foreach (T t in p)
  {
   s.Push(t);
  }
 }
}


  原来类Stack次只能Push个数据这个类Stack2扩展了Stack功能(当然也可以直接写在Stack中)他可以次把多个数据压入Stack中其中Push是个泛型思路方法这个思路方法举例如下:

  Stack<> x = Stack<>(100);
Stack2 x2 = Stack2;
x2.Push(x, 1, 2, 3, 4, 6);
s = "";
for ( i = 0; i < 5; i)
{
 s x.Pop.;
} //至此s值为64321


  泛型中静态成员变量

  在C#1.x中我们知道类静态成员变量在区别类例子间是共享并且他是通过类名访问C#2.0中由于引进了泛型导致静态成员变量机制出现了些变化:静态成员变量在相同封闭类间共享区别封闭类间不共享

  这也非常容易理解区别封闭类虽然有相同类名称但由于分别传入了区别数据类型他们是完全区别比如:

  Stack<> a = Stack<>;
Stack<> b = Stack<>;
Stack<long> c = Stack<long>;


  类例子a和b是同类型他们的间共享静态成员变量但类例子c却是和a、b完全区别类型所以不能和a、b共享静态成员变量

  泛型中静态构造

  静态构造规则:只能有且不能有参数他只能被.NET运行时自动而不能人工

  泛型中静态构造原理和非泛型类是只需把泛型中区别封闭类理解为区别类即可以下两种情况可激发静态构造:

  1. 特定封闭类第次被例子化

  2. 特定封闭类中任静态成员变量被

  泛型类中思路方法重载

  思路方法重载在.Net Framework中被大量应用他要求重载具有区别签名在泛型类中由于通用类型T在类编写时并不确定所以在重载时有些注意事项这些事项我们通过以下例子介绍说明:

  public Node<T, V>
{
 public T add(T a, V b) //第个add
 {
   a;
 }
 public T add(V a, T b) //第 2个add
 {
   b;
 }
 public add( a, b) //第 3个add
 {
   a + b;
 }
}


  上面类很明显如果T和V都传入 3个add思路方法将具有同样签名但这个类仍然能通过编译是否会引起混淆将在这个类例子化和add思路方法时判断请看下面代码:

  Node<, > node = Node<, >;
object x = node.add(2, 11);


  这个Node例子化引起了 3个add具有同样签名但却能成功他优先匹配了第 3个add但如果删除了第 3个add上面代码则无法编译通过提示思路方法产生混淆运行时无法在第个add和第 2个add的间选择

  Node<, > node = Node<, >;
object x = node.add(2, "11");


  这两行代码可正确编译传入使 3个add具有区别签名当然能找到唯匹配add思路方法



  由以上举例可知C#泛型是在例子思路方法被时检查重载是否产生混淆而不是在泛型类本身编译时检查同时还得出个重要原则:

  当般思路方法和泛型思路方法具有相同签名时会覆盖泛型思路方法

  泛型类思路方法重写

  思路方法重写(override)主要问题是思路方法签名识别规则在这点上他和思路方法重载请参考泛型类思路方法重载

  泛型使用范围

  本文主要是在类中讲述泛型实际上泛型还可以用在类思路方法、接口、结构(struct)、委托等上面使用使用思路方法大致相同就不再讲述

  小结

  C# 泛型是开发工具库中个无价的宝它们可以提高性能、类型安全和质量减少重复性编程任务简化总体编程模型而这切都是通过优雅、可读性强语法完成尽管 C# 泛型根基是 C 模板但 C# 通过提供编译时安全和支持将泛型提高到了个新水平C# 利用了两阶段编译、元数据以及诸如约束和般思路方法的类创新性概念毫无疑问C# 将来版本将继续发展泛型以便添加新功能并且将泛型扩展到诸如数据访问或本地化的类其他 .NET Framework 领域



Tags:  电脑初级入门教程 泛型编程与stlpdf 泛型编程与stl 泛型编程

延伸阅读

最新评论

发表评论