在讨论泛型之前,我们先讨论一下在没有泛型的世界里,如果我们想要创建一个独立于被包含类型的类和方法,我们需要定义objece类型,但是使用object就要面对装箱和拆箱的操作,装箱和拆箱会很损耗性能,我们接下来会用一个示例来说明使用泛型和使用非泛型对值操作时的性能差距。但是如果使用泛型,也是同样的效果,不需要装箱和拆箱的同时泛型还保证了类型安全

言归正传,.Net自2.0以后就开始支持泛型,CLR允许创建泛型引用类型和泛型值类型,但不允许创建泛型枚举类型,此外,CLR还允许创建泛型接口和泛型委托。先来简单看一下泛型的语法。

一、为什么要有泛型?

我们在写一些方法时可能会方法名相同,参数类型不同的方法,这种叫做重载。如果只是因为参数类型不同里面做的业务逻辑都是相同的,那可能就是复制粘贴方法,改变参数类型,例如一些排序算法,int、float、double等类型的排序,参数数组存的数据类型不一样,还有像根据索引找到List集合中的对象。可能这个对象是Person、Dog等对象,这样方法改变的只是参数类型,那就是能不能写一个方法,传递不同的参数类型呢?于是有了泛型。

二、什么是泛型?

泛型通过参数化类型来实现在同一份代码上操作多种数据类型。例如使用泛型的类型参数T,定义一个类Stack<T>,可以用Stack<int>、Stack<string>或Stack<Person>实例化它,从而使类Stack可以处理int、string、Person类型数据。这样可以避免运行时类型转换或封箱操作的代价和风险,类似C++的模板。泛型提醒的是将具体的东西模糊化,这与后面的反射正好相反。

三、泛型的语法

需引用命名空间System.Collections.Generic,泛型List类后面添加了一个<T>,表明操作的是一个未指定的数据类型,在泛型声明过程中,所有的类型参数放在间括号中(<>),通过逗号分隔。

命名约定

  •    泛型类型的名称用字母T作为前缀
  •     如果没有特殊要求,泛型类型允许用任意类替代,且只使用了一个泛型类型就可以用字符T作为泛型类型名称,如下
    public class List<T>{}
  • 如果类型有特定的要求(例如必须实现一个接口或者派生自基类),或者使用了两个或两个以上泛型类型,就应给泛型类型使用描述性的名称,如下
    Public class SortedList<Tkey,Tvalue>{}

 泛型和非泛型性能对比

下面的示例说明,示例对比了使用泛型和不使用泛型的对比

         static void Main(string[] args)
{
Stopwatch stopwatch = new Stopwatch();
stopwatch.Start(); // 开始监视代码运行时间
List<long> listint = new List<long>();
Add(listint);
stopwatch.Stop(); // 停止监视
TimeSpan timespan = stopwatch.Elapsed;
double milliseconds = timespan.TotalMilliseconds;
Console.WriteLine("泛型用时"+milliseconds); Stopwatch stopwatch1 = new Stopwatch();
stopwatch1.Start(); // 开始监视代码运行时间
ArrayList alistint = new ArrayList();
Addf(alistint);
stopwatch1.Stop(); // 停止监视
TimeSpan timespan1 = stopwatch1.Elapsed;
double milliseconds1 = timespan1.TotalMilliseconds;
Console.WriteLine("非泛型用时"+milliseconds1); } public static void Add(List<long> a)
{
long sum = ;
for (long i = ; i < ; i++)
{
a.Add(i);
}
foreach (var item in a)
{sum += item;}
Console.WriteLine("泛型集合结果"+sum);
} public static void Addf(ArrayList a) {
long sum = ;
for (long i = ; i < ; i++)
{
a.Add(i);
}
foreach (var item in a)
{ sum+=Convert.ToInt32(item); }
Console.WriteLine("非泛型集合结果"+sum); }

运行结果如下,在一百万次值类型循环下泛型要比非泛型省下50毫秒左右的时间,当然这点时间看起来很少,但是程序中往往不只是示例这样简单的逻辑,往往包含很对更加复杂的逻辑,时间和性能上的差距就会变得很大。

            类型安全

           从下面的例子可以看出,非泛型集合可以赋值任何类型,取出时只需要拆箱操作,泛型集合则需要赋值指定类型值,否则就会报错,从类型的安全角度来看,泛型的类型都是符合要求的类型才能放进来,所以更安全,而非泛型的集合则需要辨别其中那些是符合类型的,那些是不需要的

    List<int> list = new List<int>();//创建泛型集合
ArrayList arry = new ArrayList();//非泛型集合
list.Add();//可以
list.Add("zj");//报错 arry.Add();//可以
arry.Add("张三");//可以

   泛型类型

根据类型参数不同的指定类型实参的情况,泛型类型可以分为:

  • 如果没有为类型参数提供类型实参,那么声明的就是一个未绑定泛型类型(unbound generic)
  • 如果指定了类型实参,该类型就称为已构造类型(constructed type),然而已构造类型又可以是开放类型或封闭类型的
    • 包含类型参数的类型就是开放类型(open type)(所有的未绑定的泛型类型都属于开放类型的),
    • 每个类型参数都指定了类型实参就是封闭类型(closed type)

类型是对象的蓝图,我们可以通过类型来实例化对象;那么对于泛型来说,未绑定泛型类型是以构造泛型类型的蓝图,已构造泛型类型又是实际对象的蓝图。

下图就是一个简单的例子,Dictionary<TKey, TValue>就是一个泛型类型(未绑定泛型类型,开放类型);通过制定类型参数,可以得到不同的封闭类型;通过不同的封闭类型有可以构造不同的实例。

    泛型类型和继承

泛型类型仍然是类型,所以能从其他任何类型派生,使用泛型类型并指定类型实参时,实际上在CLR中定义新的类型对象,新的类型对象从泛型类型派生自的那个类型派生,

换句话说,List<T>是从object派生,所以List<string>,List<int>也都派生自object,类似地,由于DictionaryStringKey<TValue>派生自Dictionary<String,TValue>所以DictionaryStringKey<Guid>派生自Dictionary<String,Guid>,类型实参的指定和继承层次结构没有任何关系--理解这一点,有助于判断哪些转型是能够进行的,哪些转型是不能进行的。

接下来看代码示例:

 internal  class Node{
protected Node m_next;
public Node(Node next) {
m_next = next;
}
}
internal sealed class TypeNode<T> : Node {
public T m_data; public TypeNode(T data) : this(data, null) { } public TypeNode(T data, Node next)
: base(next)
{
m_data = data;
}
public override string ToString()
{
return m_data.ToString() +
((m_next != null) ? m_next.ToString() : null);
}
} 程序入口:
Node head = new TypeNode<Char>('.');
head = new TypeNode<DateTime>(DateTime.Now, head);
head = new TypeNode<String>("Today is ", head);
Console.WriteLine(head.ToString());
程序输出:
Today is -- ::.

定义一个非泛型的Node基类,在定义一个泛型TypedNode类(用Node类作为基类),这样依赖就可以创建一个链表,每个节点都可以是一种具体的数据类型(非Object类型),同时防止装箱,有点递归的意思。

        代码爆炸

           使用泛型类型参数的一个方法在进行JIT编译时,CLR获取方法的IL,用指定的类型实参进行替换,然后创建恰当的本地代码。然而,这样做有一个缺点:CLR要为每种不同的方法/类型组合生成本地代码。我们将这个现象称为"代码爆炸"。它可能造成引用程序集的显著增大,从而影响性能。
 
            CLR内建了一些优化措施,能缓解代码爆炸。首先,假如为一个特定的类型实参调用了一个方法,以后再次使用相同的类型实参来调用这个方法,CLR只会为这个方法/类型组合编译一次。所以,如果一个程序集使用List<DateTime>,一个完全不同的程序集也使用List<DateTime>,CLR只会为List<DateTime>编译一次方法。
 
           CLR还提供了一个优化措施,它认为所有引用类型实参都是完全相同的,所以代码能够共享。之所以能这样,是因为所有引用类型的实参或变量时间只是执行堆上的对象的指针,而对象指针全部是以相同的方式操作的。
 
          但是,假如某个类型实参是值类型,CLR就必须专门为那个值类型生成本地代码。因为值类型的大小不定。即使类型、大小相同,CLR仍然无法共享代码,可能需要用不同的本地CPU指令操作这些值。

结束语:

                   泛型的知识比预想的要多很多,一篇文章全部写完很长,会分为两个部分,将在二中继续研究泛型接口和泛型委托,协变和逆变泛型类型参数,泛型方法,和约束性

引用:http://www.cnblogs.com/wilber2013/p/4291435.html#_nav_0  田小计划 理解C#泛型

http://www.cnblogs.com/Ming8006/p/3789847.html#c.d 明-Ming 《CLR via C#》读书笔记 之 泛型

http://www.cnblogs.com/liuhailiang/archive/2012/11/26/2788642.html    Lordbaby 泛型(三)泛型类型和继承

CLR类型设计之泛型(一)的更多相关文章

  1. CLR类型设计之泛型(二)

    在上一篇文章中,介绍了什么是泛型,以及泛型和非泛型的区别,这篇文章主要讲一些泛型的高级用法,泛型方法,泛型接口和泛型委托,协变和逆变泛型类型参数和约束性,泛型的高级用法在平时的业务中用的不多,多用于封 ...

  2. CLR类型设计之参数传递

    写到这篇文章的时候,笔者回忆起来以前的开发过程中,并没有注意参数的传递是以值传递还是引用传递的,也是第一次了解到可变参数params,常用的不一定就代表理解,可能只是会用.接下来我们就一起回忆一下关于 ...

  3. CLR类型设计之方法与构造器

    无论学习那门语言都要学习函数体,C#,JAVA,PHP,都会涉及到函数体,而C#的函数体成员并不少,方法和构造器就是函数体成员之一,函数体成员还包括但不限于:方法,属性,构造器,终结器,运算符及索引器 ...

  4. CLR类型设计之类型之常量和字段

             前言 孔子说:温故而知新,可以为师矣.所以对于学习过的知识要多复习,并且每一次复习都要尽可能的去扩展,而不是书本上的几句理论知识.很多人都喜欢分享自己的学习内容,记录下生活的点点滴滴 ...

  5. CLR类型设计之属性

    在之前的随笔中,我们探讨了参数,字段,方法,我们在开始属性之前回顾一下,之前的探讨实际上串联起来就是OOP编程的思想,在接下来的文章中,我们还会讨论接口(就是行为),举个例子:我们如果要做一个学生档案 ...

  6. 【译】CLR类型加载器设计

    前言 本文翻译自BotR中的一篇,原文链接 Type Loader Design ,可以帮助我们了解CLR的类型加载机制(注意是Type类型,而不是Class类),文中涉及到术语或者容易混淆的地方,我 ...

  7. CLR via C#(16)--泛型

    泛型就像是一个模板,常常定义一些通用的算法,具体调用时再替换成实际的数据类型,提高了代码的可重用性. 一.初识泛型 1. 简单实例 以最常用的FCL中的泛型List<T >为例: stat ...

  8. [CLR via C#]12. 泛型

    泛型(generic)是CLR和编程语言提供一种特殊机制,它支持另一种形式的代码重用,即"算法重用". 简单地说,开发人员先定义好一个算法,比如排序.搜索.交换等.但是定义算法的开 ...

  9. 重温CLR(八 ) 泛型

    熟悉面向对象编程的开发人员都深谙面向对象的好处,其中一个好处是代码重用,它极大提高了开发效率.也就是说,可以派生出一个类,让他继承基类的所有能力.派生类只需要重写虚方法,或添加一些新方法,就可定制派生 ...

随机推荐

  1. Linux常见命令集锦

    这是平常用到的命令在这里做一下总结: 一.python 类1.pip(已安装)pip用来安装来自PyPI(https://www.python.org/)的python所有的依赖包,并且可以选择安装任 ...

  2. 数据库表反向生成(二) Django ORM inspectdb

    在前一篇我们说了,mybatis-generator反向生成代码. 这里我们开始说如何在django中反向生成mysql model代码. 我们在展示django ORM反向生成之前,我们先说一下怎么 ...

  3. springboot kafka集成(实现producer和consumer)

    本文介绍如何在springboot项目中集成kafka收发message. 1.先解决依赖 springboot相关的依赖我们就不提了,和kafka相关的只依赖一个spring-kafka集成包 &l ...

  4. spring mvc 多线程并发

    ThreadLocal为解决多线程程序的并发问题提供了一种新的思路.使用这个工具类可以很简洁地编写出优美的多线程程序. http://www.xuebuyuan.com/1628190.html 我们 ...

  5. java 集合之实现类ArrayList 和 LinkedList

    List 的方法列表 方法名 功能说明 ArrayList() 构造方法,用于创建一个空的数组列表 add(E e) 将指定的元素添加到此列表的尾部 get(int index) 返回此列表中指定位置 ...

  6. 使用Kotlin开发第一个Android应用

    直奔主题 第一步:为AndroidStudio安装Kotlin插件 在线安装步骤:File—>Settings—>Plugins—>Install JetBrains plugin… ...

  7. c语言入门

    c 语言现在是一门很流行的语言,它介于汇编语言和高级语言之间,我认为 它属于中级语言,如c语言 的指针 ,位操作符,等,因为接近于汇编语言,c语言的执行代码效率很高 现在大多数的系统 如unix,和l ...

  8. Javascript常见浏览器兼容问题

    常见浏览器原生javascript兼容性问题主要分为以下几类: 一.Dom 1.获取HTML元素,兼容所有浏览器方法:document.getElementById("id")以I ...

  9. 多个 (li) 标签如何获取获取选中的里面的某个特定值??

    两种方式: 1/.根据div中的class属性 指定ul 找到选中的单个li $(".f_dingdan ul li").click(function(){    var a=$( ...

  10. 网络库Alamofire使用方法学习笔记

    Github地址 由于Alamofire是swift网络库,所以,以下的所有介绍均基于swift项目 导入Alamofire 以下为使用cocoapods导入,其余的方式请参考官网 source 'h ...