• 跟小静读CLR via C#(16)泛型


    泛型就像是一个模板,常常定义一些通用的算法,具体调用时再替换成实际的数据类型,提高了代码的可重用性。


    一、初识泛型

    1. 简单实例

    以最常用的FCL中的泛型List<T >为例:

    static void Main(string[] args)
           {
               List<int> num = new List<int>();
               num.Add(1);
               num.Add(3);
               int num1 = num[0];
               int num2 = num[1];
           }

    image

    尖括号中的T是不确定的数据类型,叫做类型参数,一般规定以字母T开头,可以是TKey, TValue都可以。而调用时指定的具体类型叫做类型实参

    查看一下IL代码:

    image

    • 类型名List是以“`”加数字结尾的。数字表示类型的元数,也就是需要指定具体类型的参数个数。
    • 泛型是类型安全的。如果用“num.Add("a");”会发生编译错误;
    • 泛型可以提高算法的可重用性,而且从例子中看出int类型并没有进行装箱拆箱操作,相比将所有类型转换为Object的方式而言,提高了程序的性能。
    • 为泛型变量设置默认值时常使用default关键字进行,T temp=default(T)。如果T为引用类型,则temp为null;如果T为值类型,则temp设为0值.
    2. 开放类型与封闭类型:

    开放类型:具有泛型参数的类型是开放类型,如List<T>,CLR不允许构造开放类型的实例;

    封闭类型:在实际调用代码时,如果所有类型实参都已经指定了实际数据类型,如List<string>,则该类型为封闭类型。CLR允许构造封闭类型的实例。

    3. 类型推断:

    先看这段很常见的代码:

    image

    为了增强可读性,编译器支持类型推断功能,省略<>,我们可以将上面调用的方法改为:

    image

    * 需要注意的是,类型推断时C#使用的是变量的数据类型,而不是变量引用的对象的类型。例如:

    image

    虽然s1和s2都是指向了字符串对象,但是这两个变量的类型是不同的,所以会产生编译错误。

    二、协变和逆变泛型类型参数

    通过协变量和逆变量,可以将泛型委托或者接口的类型参数进行一定的类型转换。

    • 逆变量:泛型类型参数可以从基类转为派生类,用in关键字标识,只出现在输入位置,例如方法的参数;

    public delegate void Func<in T>(T arg);

    static void Main(string[] args)

    {

    Func<object> f1 = null;

    Func<string> f2 = f1;

    }

    • 协变量:泛型类型参数可以从派生类改为它的基类,用out关键字标识,只出现在输出位置,例如方法的返回值。

    public delegate TResult Func<out TResult>();

    static void Main(string[] args)

    {

    Func<string> fn=null;

    object result=fn();

    }

    三、泛型约束

    在设计泛型的类型参数时,可以通过where子句指定类型需要满足的约束条件。主要包含以下几种约束方式:

    1. 主要约束

    一个类型参数可以指定0或1个主要约束,主要约束可以一个非密封的引用类型,它表示类型实参必须与约束类型相同或者为约束类型的派生类。该引用类型不能为Object, Array, Delegate, MulticastDelegate, ValueType, Enum, Void。

    class Constraint1<T> where T : Stream
    {
         public void Close(T stream)
         {
             stream.Close();
         }
    }

    class Program
    {
         static void Main(string[] args)
         {
             Constraint1<FileStream> s2 = new Constraint1<FileStream>();
         }
    }

    两种特殊的主要约束:classstruct

    • Class约束:要求指定的类型实参必须是引用类型。Where T:class

    image

    在没有约束的情况下,如果T为值类型,是不能赋值为null的,所以会产生编译错误。添加约束后编译通过:

    image

    • Struct 约束:要求指定的类型实参必须是值类型

    image

    在没有约束的情况下,如果T为引用类型是不能声明为可空值类型的,所以会产生编译错误。添加struct约束后运行正常:

    image

    2. 次要约束

    一个类型参数可以指定0或者多个次要约束。常见的次要约束主要有两种:

    • 接口约束:类型实参必须实现了指定的所有接口。例如:

    image

    接口约束的另外一个好处是:值类型实参调用接口方法时不用进行装箱操作。

    • 类型参数约束:在指定的类型实参之间,存在着一定关系。例如要求存在继承关系:

    image

    3. 构造器约束

    构造器约束要求类型实参必须实现了无参构造器,而且它不支持有参构造器。

    image

    你也许喜欢:跟小静读CLR via C#(00)-开篇及目录

  • 相关阅读:
    H5实现查看图片和删除图片的效果
    HTTP 状态码(HTTP Status Code)
    HashMap详解(基于JDK 1.8)
    FutureTask详解
    ForkJoin框架详解
    final详解
    磁盘管理之inode与block
    如何在CentOS上安装一个2048小游戏
    Linux帮助手册(man)
    Linux入门-基础命令
  • 原文地址:https://www.cnblogs.com/janes/p/2295959.html
Copyright © 2020-2023  润新知