浅谈.NET中泛型的基本原理

  本片继承前面几篇一贯的特点,浅谈胡侃。

  1 .NET为什么要引入泛型?

  说到.NET泛型,应该都不陌生,毕竟泛型是.NET 2.0中就推出的特性,各位博友应该都知道引入泛型的最主要目的是为了解决装箱、拆箱带来的性能损失,说的当然没有错,但是不够“太具体”,确切来讲泛型解决了原先无法避免的容器操作的装箱拆箱问题

  目的就说这么多吧,言简意赅,该说的说了,多说无益。

  2.浅谈.NET泛型原理

  有过C++编程经验的博友对C++中的模板,一定不陌生,泛型的语法和概念和C++中的模板极其类似,在C++中模板的目的是为了设计更加通用的类型,在.NET中也是这样,当然还有另外一个重要的作用,就是前面所说的:避免容器操作中的装箱和拆箱操作!

  先写一个一段简单的代码来示例下,代码如下:

using System;

namespace 浅谈泛型的示例
{
    /// <summary>
    /// DebugLZQ
    /// http://www.cnblogs.com/DebugLZQ
    /// 定义一个简单泛型类
    /// </summary>
    public class SimpleGenericClass<T>
    {
        T my;
        public SimpleGenericClass(T t)
        {
            my = t;
        }
        public override string ToString()
        {
            return my.ToString();
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            SimpleGenericClass<string> genericClass = new SimpleGenericClass<string>("DebugLZQ");
            Console.WriteLine(genericClass);

            Console.ReadKey();
        }
    }
}

  程序运行结果如下:

  首先,程序申明了一个泛型(类型)SimpleGenericClass<T>。注意编码规范:和所有的接口名称都以I开头一样所有的泛型参数都以T开头。

  不要被泛型表面的复杂迷惑,和.NET其他类型一样,带泛型参数的类型同样是一个确定的类型,在不被指定的情况下,它直接继承自System.Object类型,并且可以派生出其他类型。
  但是泛型类型和普通的类型还是有一定的区别的。通常泛型类型被称为“开放式类型”,.NET机制规定开放式类型不能被实例化,这样就确保了泛型(开放式类型)在参数类型被指定之前,不能被实例化成任何对象。 实际上,.NET也没有办法进行实例化,因为不能确定需要分配多少内存给开放式类型。 

  然后在Main方法中,指定了开放类型(泛型)的参数,这个时候重新定义了一个新的封闭类型SimpleGenericClass<string> ,针对该类型的所有实例化都是合法的。注意,虽然为开放类型提供泛型的参数导致了一个新的封闭类型的生成,但这不代表新的封闭类型和开放类型由任何派生继承的关系,事实上,两者在类结构上处于同一个层次,并且两者没有任何关系。

  最后说明下:在System.Collections.Generic名称空间下定义了一些诸如List<>、Dictionary<,>等泛型容器,并且在System.Array中定义了一些静态的泛型方法,MS鼓励使用新的泛型容器来代替.NET版中的容器和方法,以提高程序的性能。

  3.CLR层面上的泛型

  下面是lz回复博友清风的,内容大概是lz从CLR层面上理解的泛型。大家批评指正下:

  @清風揚諰
  你给出了lz一个不同的理解切入点,清风你这个是要把泛型的CLR本质拉出来啊,关于.NET泛型的原理lz暂切写到这,同时lz建议清风兄整理下,lz拜读~这样叫lz来讲的话,估计我只能说个大概而且不一定正确,lz肯定需要去翻clr via c#之类的参考书了~
  如果一定要从CLR层面来讲的话,lz的理解大概是这样的:泛型是基于JIT的,由CLR运行时支持的。代码经过第一次C#编译器编译后,产生的IL是并没有为T指定一个特定的类型,这个lz在文中有说过泛型这种开放类型,不能被实例化,当然这时候T还是T。真正的泛型实例化,发生在JIT编译时,产生的机器码取决于给T传入的类型。
  对于值类型,JIT把IL中的参数T,替换为传入的值类型,并且编译为本机代码。由于值类型的操作,就是操作数据本身。JIT不会为不知道大小的参数生成相同的本机代码,因此在以后的场合中,JIT都会去寻找是否存在了相同的代码,如果是就不会重新编译,而是引用现有的代码(注意如果传入的是不同的值类型参数,当然生成不同的代码!)。因为是引用也就没有了C++中的代码内联造成的“代码爆炸”问题。
对于引用类型,JIT把T替换为Object,由于引用类型变量就是一个指向(托管)堆的某个地址的指针,对于不同指针的操作完全可以采用相同的方式,因为都是指针。因此引用类型共用一个代码。
  总之:参数是值类型的是不同的类型生成不同的代码,生成某个类型代码后,使用引用;引用类型是共用一个代码,指针操作嘛!同时lz前面说到:泛型,是基于JIT的,也就意味着无论是值类型还是引用类型都将共用一个占位符,引用的是同一个类型下的方法和方法槽表。
  就这么大概得回复下清风吧,觉得大家交流起来还是很有意思的嘛~

  4.一点补充

  下面是博友 kennywangjin的一些补充:

作一点补充,献丑:
  事实上,泛型对性能的提升仅仅对值类型有效,对引用类型几乎可以忽略不计,因为引用类型不需要装箱拆箱(不严谨)。
引用类型的泛型可以共用代码,而对于值类型来说,JIT会为每种值类型生成各自的封闭类型,并不会节省代码量,事实上,还会加重JIT编译的负担——尽管几乎可以忽略不计。
  个人认为,泛型真正的优势在于编译时类型安全,结合IDE的智能提示,极大地提高了编码效率,至于性能,试想下,我们有多少情况会用到纯粹值类型的泛型呢?大部分情况下都还是引用类型的泛型。
  甚至在某些极端情况下,泛型有略微降低性能的情况——对于引用类型来说。但这些依然不能妨碍其成为.NET平台最有意义的一次升级之一。

 请点击下面的绿色通道---关注DebugLZQ,共同交流进步~

posted @ 2012-09-03 20:32  DebugLZQ  阅读(10925)  评论(31编辑  收藏  举报