本文内容

  • 使用泛型约束的原因
  • 未绑定的类型参数
  • 作为约束的类型参数
  • 参考资料

当“设计模式”出现时,人们提“用接口编程”;后来,有了泛型,人们提“用泛型编程”。什么泛型?比如,单链表 LinkedList 场景,每个节点包含两个字段:值和下一个节点的引用,其中,“值”既可以是 int,也可以是 string,甚至是对象,为每个数据类型都写一个类,显然太麻烦,此时就可以使用泛型 LinkedList <T>T 表示 intstring 类型等等;再如,排序算法中很常见 Swap(ref int a, ref int b) 函数,可以交换两个 int 类型,当然也可以是 string,用泛型也很合适。用 T 代表 intstring,甚至任何类型。

但问题是,实际项目中用 T 表示任何类型,显然太粗放。比如,要是用表示动物和植物,动物和植物可能是接口或基类,显然动物和植物不同,顶多都继承生物基类或接口,我们倒是希望把 T 限定在动物或植物,这样在定义相应的泛型类中就可以使用动物或植物的成员——这就是泛型约束。

这就完美了~

所以,实际项目中T 往往不是任何类型,而是代表某个类型、某个基类、某个接口,说是任何类型,只是泛型表达自己的理念而已。

下载 Demo

使用泛型约束的原因


如果把 T 限定在某个基类、某个接口上,那么泛型类中就可以使用那个基类或接口中的成员。

如果要检查泛型列表中的某个项以确定它是否有效,或者将它与其他某个项进行比较,则编译器必须在一定程度上保证它需要调用的运算符或方法将受到客户端代码可能指定的任何类型参数的支持。 这种保证是通过对泛型类定义应用一个或多个约束获得的。

例如,基类约束告诉编译器:仅此类型的对象或从此类型派生的对象才可用作类型参数。 一旦编译器有了这个保证,它就能够允许在泛型类中调用该类型的方法。约束是使用关键字 where

public class Employee

{

    private string name;

    private int id;

 

    public Employee(string s, int i)

    {

        name = s;

        id = i;

    }

 

    public string Name

    {

        get { return name; }

        set { name = value; }

    }

 

    public int ID

    {

        get { return id; }

        set { id = value; }

    }

}

/// <summary>

/// 员工单链表

/// </summary>

/// <typeparam name="T"></typeparam>

public class EmployeeList<T> where T : Employee

{

    /// <summary>

    /// Employee 节点 

    /// </summary>

    private class Node

    {

        private Node next;

        private T data;

 

        public Node(T t)

        {

            next = null;

            data = t;

        }

 

        public Node Next

        {

            get { return next; }

            set { next = value; }

        }

 

        public T Data

        {

            get { return data; }

            set { data = value; }

        }

    }

 

    private Node head;

 

    public EmployeeList()

    {

        head = null;

    }

 

    public void AddHead(T t)

    {

        Node n = new Node(t);

        n.Next = head;

        head = n;

    }

 

    public IEnumerator<T> GetEnumerator()

    {

        Node current = head;

 

        while (current != null)

        {

            yield return current.Data;

            current = current.Next;

        }

    }

 

    public T FindFirstOccurrence(string s)

    {

        Node current = head;

        T t = null;

 

        while (current != null)

        {

            //The constraint enables access to the Name property.

            if (current.Data.Name == s)

            {

                t = current.Data;

                break;

            }

            else

            {

                current = current.Next;

            }

        }

        return t;

    }

}

where T : Employee”约束使泛型类可以使用 Employee.Name 属性,即 current.Data.Name

类型为 T 的所有项,都保证是 Employee 对象或从 Employee 继承的对象。

编译器除了假设类型参数派生自 System.Object 以外,不会做其他任何假设。在希望强制两个类型参数之间的继承关系的情况下,可对泛型类使用参数类型约束。

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

约束

说明

T:结构

类型参数必须是值类型。

T:类

类型参数必须是引用类型;这一点也适用于任何类、接口、委托或数组类型。

T:new()

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

T:<基类名>

类型参数必须是指定的基类或派生自指定的基类。

T:<接口名称>

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

T:U

为 T 提供的类型参数必须是为 U 提供的参数或派生自为 U 提供的参数。

可以对同一类型参数应用多个约束,而且约束自身可以是泛型类型,如下所示:

class EmployeeList<T> where T : Employee, IEmployee, System.IComparable<T>, new()

{

    // ...

}

这样就可以增加约束类型及其继承层次结构中的所有类型所支持的允许操作和方法。 因此,在设计泛型类或方法时,如果要对泛型成员执行除简单赋值之外的任何操作或调用 System.Object 不支持的任何方法,您将需要对该类型参数应用约束。

在应用 where T : class 约束时,避免对类型参数使用 == 和 != 运算符,因为这些运算符仅测试引用是否相等,而不不是值是否相等。即使在用作参数的类型中重载这些运算符也是如此。下面代码说明了这一点:即使 String 类重载 == 运算符,输出也为 false。

public static void OpTest<T>(T s, T t) where T : class

{

    System.Console.WriteLine(s == t);

}

static void Main()

{

    string s1 = "target";

    System.Text.StringBuilder sb = new System.Text.StringBuilder("target");

    string s2 = sb.ToString();

    OpTest<string>(s1, s2);

}

因为编译器在编译时仅知道 T 是引用类型,因此必须使用对所有引用类型都有效的默认运算符。这就好像对 int 类型和 string 类型的比较,显然不同。

如果必须测试值是否相等,那么可以使用 where T : IComparable<T> 约束,并在泛型类中实现该接口。

 

未绑定的类型参数


没有约束的类型参数(如公共类 SampleClass<T>{} 中的 T)称为未绑定的类型参数。 未绑定的类型参数具有以下规则:

  • 不能使用 != 和 == 运算符,因为无法保证具体类型参数能支持这些运算符。
  • 可以在它们与 System.Object 之间转换,或将它们显式转换为任何接口类型。
  • 可以将它们与 null 进行比较。将未绑定的参数与 null 进行比较时,如果类型参数为值类型,则该比较将始终返回 false。

 

作为约束的类型参数


泛型类有泛型类型参数,泛型类的成员函数也有自己的泛型参数,但成员函数的泛型参数要约束在泛型类型参数上,此时就很用,如下示例所示:

class List<T>

{

    void Add<U>(List<U> items) where U : T {/*...*/}

}

上面示例中,泛型类型参数 T 在其成员函数 Add 方法中有一个类型约束 where U : T,其中,Add 方法中使用了泛型 U,而在 List 类中并没有绑定的类型参数,没有约束。

类型参数还可在泛型类定义中用作约束。注意,必须在尖括号中声明此类型参数与任何其他类型的参数:

//Type parameter V is used as a type constraint.

public class SampleClass<T, U, V> where T : V { }

 

参考资料


 

下载 Demo

.Net 泛型约束的更多相关文章

  1. 06.C#泛型约束和高级泛型(三章3.3-3.4)

    吃午饭前继上篇泛型再写一篇关于泛型的文章,虽然总是被博客园移出首页,文章不精确实是大问题啊,会再接再厉的.进入正题. 先来说下泛型约束.当我们在使用泛型的时候通常会希望使用泛型实参时,参数能具备某一些 ...

  2. 转:C# 泛型编程之泛型类、泛型方法、泛型约束

    C# 泛型编程之泛型类.泛型方法.泛型约束 分类: asp.net c#2012-08-07 17:36 5998人阅读 评论(0) 收藏 举报 c#编程classobject编译器struct 泛型 ...

  3. 泛型约束new()的使用

    下面泛型约束代码,where字句后面有new()约束,T类型必须有公有的无参的构造函数. private T InternalCreate<T>() where T : IObjectWi ...

  4. 用LinQ扩展方法,泛型扩展方法,实现自定义验证字符是否空、对象是否为null,及泛型约束使用,Action的使用

    一.Linq扩展方法 1.扩展方法必须是静态方法.扩展方法所在的类必须是静态类 2.扩展方法里面的参数必须制定this关键字,紧跟需要扩展的类型,如下: 二.泛型约束 1.使用泛型的原因,是在不知道需 ...

  5. C# 泛型约束 xxx Where T:约束(二)

    泛型是什么? 通过上篇的实例  C# 泛型约束 xxx<T> Where T:约束(一),我们对泛型有一定的认识. 所谓泛型,即通过参数化类型来实现在同一份代码上操作多种数据类型,泛型编程 ...

  6. C# 泛型约束 xxx<T> Where T:约束(一)

    泛型约束 代码举例 发现我们游戏的代码中,主程写了很多类似这样的代码: public static T CreateObject<T>(out int objectId) where T ...

  7. C#中泛型类,泛型方法,泛型约束实际应用

    前言 所谓泛型,即通过参数化类型来实现在同一份代码上操作多种数据类型. 泛型编程是一种编程范式,它利用“参数化类型”将类型抽象化,从而实现更为灵活的复用.在定义泛型类时,在对客户端代码能够在实例化类时 ...

  8. C# 泛型编程之泛型类、泛型方法、泛型约束

    来自Hauk的文章 C# 泛型编程之泛型类.泛型方法.泛型约束 所谓泛型,即通过参数化类型来实现在同一份代码上操作多种数据类型. 泛型编程是一种编程范式,它利用“参数化类型”将类型抽象化,从而实现更为 ...

  9. 泛型约束-swift

    1.泛型定义本体有参量类型约束: 2.泛型扩展对参量类型约束: 3.函数参量约束: 泛型类型的访问控制: 1.与类型无关的通用函数,泛型的任何实例都可以访问: 2.与类型有关的函数(通过扩展约束实现) ...

随机推荐

  1. CGI-- FASTCGI

    http://blog.csdn.net/sweatott/article/details/54913151 CGI:是 Web Server 与 Web Application 之间数据交换的一种协 ...

  2. /bin/sh^M: bad interpreter:没有那个文件或目录解决

    /bin/sh^M: bad interpreter:没有那个文件或目录解决   执行脚本时发现如下错误: /bin/sh^M: bad interpreter: 没有那个文件或目录   错误分析: ...

  3. uifont 字体详解

    时间2013-06-04 11:26:33 CSDN博客原文  http://blog.csdn.net/u010013695/article/details/9020611 我们在开发中很多时候要设 ...

  4. 配置Oracle E-Business Suite Integrated SOA Gateway Release 12.1.2/12.1.3

    3.3 配置Oracle E-Business Suite Integrated SOA Gateway Release 12.1.2 注意: 在多节点环境上配置Oracle E-Business S ...

  5. 架构:The Onion Architecture : part 2(洋葱架构:第二篇)(转载)

    原位地址:http://jeffreypalermo.com/blog/the-onion-architecture-part-2/. In part 1, I introduced an archi ...

  6. SharePoint 应用程序页匿名

    前言 最近,有朋友问开发应用程序页,都是需要先登录再访问,无法开发匿名的应用程序页. 解决方法 其实,SharePoint帮我们提供了匿名访问的应用程序页的方法,只是和普通应用程序页继承的基类不一样, ...

  7. Variational Inference

    作者:孙九爷链接:https://www.zhihu.com/question/41765860/answer/101915528来源:知乎著作权归作者所有.商业转载请联系作者获得授权,非商业转载请注 ...

  8. Git项目的初始化

    快速设置— 如果你知道该怎么操作,直接使用下面的地址 git@github.com:username/myproject.git 我们强烈建议所有的git仓库都有一个README, LICENSE,  ...

  9. PHP Curl transfer closed with outstanding read data remaining

    php transfer closed with outstanding read data remaining php curl CURLOPT_HTTPHEADER php curl 报错 tra ...

  10. [转]PHP用mysql数据库存储session

    From : http://www.php100.com/html/webkaifa/PHP/PHPyingyong/2010/0226/4002.html 大部分使用php的人一旦应用到sessio ...