之前对于泛型一直没有个系统的学习,只是懂得如何调用而已,今天就结合MSDN和网上的一些文章对C#2.0的泛型进行深入研究。

1. 概述

  泛型是 2.0 版 C# 语言和公共语言运行库 (CLR) 中的一个新功能。一般用于模块的功能非常相似,只因为参数类型不同。可能你会想到用Object不就好了?但处理值类型时,会出现装箱、折箱操作,这将在托管堆上分配和回收大量的变量,若数据量大,则性能损失非常严重。泛型将类型参数的概念引入 .NET Framework,类型参数使得设计如下类和方法成为可能:这些类和方法将一个或多个类型的指定推迟到客户端代码声明并实例化该类或方法的时候。例如,通过使用泛型类型参数 T,您可以编写其他客户端代码能够使用的单个类,而不致引入运行时强制转换或装箱操作的成本或风险。在类实例化时指定T的类型,运行时(Runtime)自动编译为本地代码,运行效率和代码质量都有很大提高,并且保证数据类型安全。

如下

public class GenericList<T>

{

void Add(T input) { }

}

class TestGenericList

{

private class ExampleClass { }

static void Main()

{

GenericList<int> list1 = new GenericList<int>();

GenericList<string> list2 = new GenericList<string>();

GenericList<ExampleClass> list3 = new GenericList<ExampleClass>();

}

}

2. 泛型的使用

  泛型有泛型接口、泛型类、泛型方法、泛型事件和泛型委托。

2.1泛型类型参数

  在泛型类型或方法定义中,类型参数是客户端在实例化泛型类型的变量时指定的特定类型的占位符。上述泛型类中列出的 GenericList<T>)不可以像这样使用,因为它实际上并不是一个类型,而更像是一个类型的蓝图。若要使用 GenericList<T>,客户端代码必须通过指定尖括号中的类型参数来声明和实例化构造类型。此特定类的类型参数可以是编译器识别的任何类型。可以创建任意数目的构造类型实例,每个实例使用不同的类型参数,如下所示:

GenericList<float> list1 = new GenericList<float>();

GenericList<ExampleClass> list2 = new GenericList<ExampleClass>();

2.1.1类型参数的约束

  在定义泛型类时,可以对客户端代码能够在实例化类时用于类型参数的类型种类施加限制。如果客户端代码尝试使用某个约束所不允许的类型来实例化类,则会产生编译时错误。这些限制称为约束。约束是使用 where 上下文关键字指定的。下表列出了六种类型的约束:

约束
说明

T:结构
类型参数必须是值类型。可以指定除 Nullable 以外的任何值类型。有关更多信息,请参见使用可空类型(C# 编程指南)。

T:类
类型参数必须是引用类型,包括任何类、接口、委托或数组类型。

T:new()
类型参数必须具有无参数的公共构造函数。当与其他约束一起使用时,new() 约束必须最后指定。

T:<基类名>
类型参数必须是指定的基类或派生自指定的基类。

T:<接口名称>
类型参数必须是指定的接口或实现指定的接口。可以指定多个接口约束。约束接口也可以是泛型的。

T:U
为 T 提供的类型参数必须是为 U 提供的参数或派生自为 U 提供的参数。这称为裸类型约束。

例如public class GenericList<T> where T : Employee

  意思就是约束使得泛型类能够使用 Employee.Name 属性,因为类型为 T 的所有项都保证是 Employee 对象或从 Employee 继承的对象。 具体参照http://msdn2.microsoft.com/zh-cn/library/d5x73970(VS.80).aspx

2.1.2类型的命名规则

  务必使用描述性名称命名泛型类型参数,除非单个字母名称完全可以让人了解它表示的含义,而描述性名称不会有更多的意义。

  考虑使用 T 作为具有单个字母类型参数的类型的类型参数名。

  务必将“T”作为描述性类型参数名的前缀。public interface ISessionChannel<TSession> { TSession Session { get; } }

2.2 泛型类

  一般情况下,创建泛型类的过程为:从一个现有的具体类开始,逐一将每个类型更改为类型参数,直至达到通用化和可用性的最佳平衡。创建您自己的泛型类时,需要特别注意以下事项:

  ·将哪些类型通用化为类型参数。

  一般规则是,能够参数化的类型越多,代码就会变得越灵活,重用性就越好。但是,太多的通用化会使其他开发人员难以阅读或理解代码。

  ·如果存在约束,应对类型参数应用什么约束(请参见类型参数的约束(C# 编程指南))。

  一个有用的规则是,应用尽可能最多的约束,但仍使您能够处理需要处理的类型。例如,如果您知道您的泛型类仅用于引用类型,则应用类约束。这可以防止您的类被意外地用于值类型,并允许您对 T 使用 as 运算符以及检查空值。

  ·是否将泛型行为分解为基类和子类。

  由于泛型类可以作为基类使用,此处适用的设计注意事项与非泛型类相同。有关从泛型基类继承的规则,请参见下面的内容。

  ·是否实现一个或多个泛型接口。

  例如,如果您设计一个类,该类将用于创建基于泛型的集合中的项,则可能需要实现一个接口,如 IComparable<T>,其中 T 是您的类的类型。

2.2.1泛型类中的静态构造函数

  静态构造函数的规则:只能有一个,且不能有参数,他只能被.NET运行时自动调用,而不能人工调用。

  泛型中的静态构造函数的原理和非泛型类是一样的,只需把泛型中的不同的封闭类理解为不同的类即可。以下两种情况可激发静态的构造函数:

  1. 特定的封闭类第一次被实例化。

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

2.2.2 泛型类中的静态成员变量

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

  这也非常容易理解,因为不同的封闭类虽然有相同的类名称,但由于分别传入了不同的数据类型,他们是完全不同的类。

比如

GenericList<int> list1 = new GenericList<int>();

GenericList<string> list2 = new GenericList<string>();

  由于list1和list2是不同的类型,所以不能共享静态成员变量

2.2.3泛型类中的方法重载

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

public class Node<T, V>

{

public T add(T a, V b) //第一个add

{

return a;

}

public T add(V a, T b) //第二个add

{

return b;

}

public int add(int a, int b) //第三个add

{

return a + b;

}

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

Node<int, int> node = new Node<int, int>();

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

  这个Node的实例化引起了三个add具有同样的签名,但却能调用成功,因为他优先匹配了第三个add。但如果删除了第三个add,上面的调用代码则无法编译通过,提示方法产生的混淆,因为运行时无法在第一个add和第二个add之间选择。

Node<string, int> node = new Node<string, int>();

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

这两行调用代码可正确编译,因为传入的string和int,使三个add具有不同的签名,当然能找到唯一匹配的add方法。

  由以上示例可知,C#的泛型是在实例的方法被调用时检查重载是否产生混淆,而不是在泛型类本身编译时检查。同时还得出一个重要原则:

  当一般方法与泛型方法具有相同的签名时,会覆盖泛型方法。

2.3 泛型接口

  为泛型集合类或表示集合中项的泛型类定义接口通常很有用。对于泛型类,使用泛型接口十分可取,例如使用 IComparable<T> 而不使用 IComparable,这样可以避免值类型的装箱和取消装箱操作。.NET Framework 2.0 类库定义了若干新的泛型接口,以用于 System.Collections.Generic 命名空间中新的集合类。

  将接口指定为类型参数的约束时,只能使用实现此接口的类型。下面的代码示例显示从 GenericList<T> 类派生的 SortedList<T> 类。有关更多信息,请参见泛型介绍(C# 编程指南)。SortedList<T> 添加了约束 where T : IComparable<T>。这将使 SortedList<T> 中的 BubbleSort 方法能够对列表元素使用泛型 CompareTo 方法。在此示例中,列表元素为简单类,即实现 IComparable<Person> 的 Person。

  代码这里就不详细了,详情见http://msdn2.microsoft.com/zh-cn/library/kwtft8ak(VS.80).aspx

public class GenericList<T> : System.Collections.Generic.IEnumerable<T>

{

public System.Collections.Generic.IEnumerator<T> GetEnumerator()

{ Node current = head;

while (current != null)

{

yield return current.Data; current = current.Next;

}

}

}

public class SortedList<T> : GenericList<T> where T : System.IComparable<T>

{

public class Person : System.IComparable<Person>。。。。。。。。。。。。。。。。。。。。。。

  可将多重接口指定为单个类型上的约束,如下所示:

class Stack<T> where T : System.IComparable<T>, IEnumerable<T> { }

  一个接口可定义多个类型参数,如下所示:

interface IDictionary<K, V> { }

  类之间的继承规则同样适用于接口

  如果泛型接口为逆变的,即仅使用其类型参数作为返回值,则此泛型接口可以从非泛型接口继承。在 .NET Framework 类库中,IEnumerable<T> 从 IEnumerable 继承,因为 IEnumerable<T> 仅在 GetEnumerator 的返回值和当前属性 getter 中使用 T。

  具体类可以实现已关闭的构造接口

  只要类参数列表提供了接口必需的所有参数,泛型类便可以实现泛型接口或已关闭的构造接口

2.4 泛型委托

  委托 可以定义自己的类型参数。引用泛型委托的代码可以指定类型参数以创建已关闭的构造类型,就像实例化泛型类或调用泛型方法一样,如下例所示:

public delegate void Del<T>(T item);

public static void Notify(int i) { }

Del<int> m1 = new Del<int>(Notify);

  C# 2.0 版具有称为方法组转换的新功能,此功能适用于具体委托类型和泛型委托类型,并使您可以使用如下简化的语法写入上一行

Del<int> m2 = Notify;

  引用委托的代码必须指定包含类的类型变量。

  在泛型类内部定义的委托使用泛型类类型参数的方式可以与类方法所使用的方式相同。

C#2.0中的泛型学相关推荐

  1. ASP.NET Core 3.1 Web API和EF Core 5.0 中具有泛型存储库和UoW模式的域驱动设计实现方法

    目录 介绍 背景 领域驱动设计 存储库模式 工作单元模式 使用代码 创建空白解决方案和解决方案架构 添加和实现应用程序共享内核库 PageParam.cs 在Entity Framework Core ...

  2. .NET 4.0中的泛型协变和反变

    随Visual Studio 2010 CTP亮相的C#4和VB10,虽然在支持语言新特性方面走了相当不一样的两条路:C#着重增加后期绑定和与动态语言相容的若干特性,VB10着重简化语言和提高抽象能力 ...

  3. [读书笔记]C#学习笔记七: C#4.0中微小改动-可选参数,泛型的可变性

    前言 下面就开始总结C#4.0的一些变化了, 也是这本书中最后的一点内容了, 这一部分终于要更新完了. 同时感觉再来读第二遍也有不一样的收获. 今天很嗨的是武汉下雪了,明天周六,一切都是这么美好.哈哈 ...

  4. [翻译]C#中的泛型 (From dotNet SDK 2.0 Beta1)

    来源:Mircrosoft.NET 2.0 Beta1 SDK 翻译:Jim Xu 日期: 2004-11-2 泛型(generic)是C#语言2.0和通用语言运行时(CLR)的一个新特性.泛型为.N ...

  5. 《从零开始学Swift》学习笔记(Day 7)——Swift 2.0中的print函数几种重载形式

    原创文章,欢迎转载.转载请注明:关东升的博客 Swift 2.0中的print函数有4种重载形式: print(_:).输出变量或常量到控制台,并且换行. print(_:_:).输出变量或常量到指定 ...

  6. [转贴]从零开始学C++之STL(二):实现一个简单容器模板类Vec(模仿VC6.0 中 vector 的实现、vector 的容量capacity 增长问题)...

    首先,vector 在VC 2008 中的实现比较复杂,虽然vector 的声明跟VC6.0 是一致的,如下: C++ Code  1 2   template < class _Ty, cla ...

  7. java如何用反射把具体方法抽象_如何在Java 中使用泛型或反射机制对DAO进行抽象...

    如何在Java 中使用泛型或反射机制对DAO进行抽象 发布时间:2020-11-26 16:07:42 来源:亿速云 阅读:80 作者:Leah 本篇文章为大家展示了如何在Java 中使用泛型或反射机 ...

  8. Java中创建泛型数组

    Java中创建泛型数组 使用泛型时,我想很多人肯定尝试过如下的代码,去创建一个泛型数组 T[] array = new T[]; 当我们写出这样的代码时编译器会报Cannot create a gen ...

  9. 技术图文:C#语言中的泛型 II

    C#语言中的泛型 II 知识结构: 6. 泛型接口 泛型类与泛型接口结合使用是很好的编程习惯,比如用IComparable<T>而非IComparable,以避免值类型上的装箱和拆箱操作. ...

最新文章

  1. TensorFlowMNIST数据集逻辑回归处理
  2. 对象的指针指向哪里,oc的类信息存放在哪里
  3. 【BLE】BLE中常用的UUID(标准)
  4. 程序员30岁后,9分钟跑完1600米
  5. 用 docker-compose 启动 WebApi 和 SQL Server
  6. Qt之tcp的简单使用
  7. 将xml 写到内存中再已string类型读出来
  8. 几款主流电子电路仿真软件优缺点比较
  9. WPS文档目录更新产生的问题记录
  10. 车机屏幕适配方案总结
  11. 到底什么是CE、C++、C+L波段?
  12. java 计算毫秒差值,关于时间的操作(Java版)——获取给定时间与目前系统时间的差值(以毫秒为单位)...
  13. 【DB笔试面试608】在Oracle中,如何使用STA来生成SQL Profile?
  14. flutter去除阻尼效果
  15. 利用C++实现简单的文件加密
  16. 数字图像处理--傅里叶(逆)变换
  17. Linux下异步IO(libaio)的使用以及性能
  18. bzoj2121 字符串游戏
  19. 【实验2 选择结构】7-3 sdut-C语言实验-时间间隔
  20. 我的物联网大学【第四章】:江湖人物志之初创团队

热门文章

  1. 中国工程院院士徐宗本:大数据的挑战和问题
  2. 理解 python 装饰器
  3. redis集群添加master节点
  4. iOS开发小技巧--学会包装控件(有些view的位置由于代码或系统原因,位置或者尺寸不容易修改或者容易受外界影响)...
  5. Windows下Redmine插件安装
  6. Linux运维系统工程师系列---11
  7. 【转载】asp.net中弹出确认窗口(confirm),实现删除确认的功能
  8. [导入]ASP常用函数:doAlert()
  9. Nginx利用nginx_upstream_check_module检查后端健康情况
  10. SpringMVC 日期类型转换