在讨论泛型之前,我们先讨论一下在没有泛型的世界里,如果我们想要创建一个独立于被包含类型的类和方法,我们需要定义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. vue2组件之select2调用

    目前,项目中使用了纯前端的静态项目+RESTFul接口的模式.为了更好的对数据进行操作,前端使用了vue2的mvvm功能,但是由于不是单页面应用,所以,并没有涉及到其它的如vue-route等功能,也 ...

  2. 基于java的后台截图功能的实现

    Java后台截图功能的实现 背景介绍: 在近期开发的可视化二期项目中的邮件项目中,邮件中的正文中含有图片.该图片的产生是将一些html网页转为图片格式,刚开始考虑使用第三方组件库html2image和 ...

  3. 【懒人有道】在asp.net core中实现程序集注入

    前言 在asp.net core中,我巨硬引入了DI容器,我们可以在不使用第三方插件的情况下轻松实现依赖注入.如下代码: // This method gets called by the runti ...

  4. WinForm程序的发布

  5. win10 uwp 反射

    本文在h神的指导下完成. 反射是强大的好用的,我们可以添加新功能不修改之前的代码,通过使用反射得到. 本文下面和大家说如何做一个和WPF一样的反射功能,如何才能获的 UWP 程序集所有类. 先来说下反 ...

  6. 新博客,新开始-从Chrome浏览器奔溃说起

    新博客,新开始 今天是2015-04-09,昨天新开的博客,今天在这写上一段,算是立个标记,好留以后拿来回溯吧. 不知道是谁跟我说的,坚持写博客是个好习惯,也能帮助自己总结经验,提高技术.也许大概可能 ...

  7. samba服务:为在windows下操作linux的文件而生

    vi/vim编辑器好玩吗?虽有着层出不穷的语法糖但又如何与传统的sublime相媲美? 那么,来吧~ 动手跟我一起做个samba服务吧~   安装   yum -y install samba  配置 ...

  8. 【深度学习系列】PaddlePaddle之手写数字识别

    上周在搜索关于深度学习分布式运行方式的资料时,无意间搜到了paddlepaddle,发现这个框架的分布式训练方案做的还挺不错的,想跟大家分享一下.不过呢,这块内容太复杂了,所以就简单的介绍一下padd ...

  9. .Net Web开发技术栈

    有很多朋友有的因为兴趣,有的因为生计而走向了.Net中,有很多朋友想学,但是又不知道怎么学,学什么,怎么系统的学,为此我以我微薄之力总结归纳写了一篇.Net web开发技术栈,以此帮助那些想学,却不知 ...

  10. Cocos 2d-X Lua 游戏添加苹果内购(一) 图文详解准备流程

    事前准备 最近给游戏添加了苹果的内购,这一块的东西也是刚刚做完,总结一下,其实这里不管是游戏还是我们普通的App添加内购这一块的东西都是差不多的,多出来的部分就是我们Lua和OC的交互的部分,以前刚开 ...