• C# 泛型约束


    一、泛型简介
    1.1泛型
    通过使用泛型,可以创建这样的类、接口和方法,它们以一种类型安全的工作方式操作各种数据。
    本质上,术语“泛型”指的是“参数化类型”(parameterized types)。参数化类型非常重要,因为它们可以在创建类、接口、方法和委托的时候将要操作的数据类型作为参数进行指定。
    通过泛型,可以创建一个类,使其自动处理不同类型的数据。使用参数化类型的类、接口、方法和委托都可以称为“泛型”。
    可以通过使用object类型的引用来创建通用的类、接口、方法和委托。但这种方法的缺陷在于,无法保证类型安全。泛型弥补了无法保证类型安全的缺陷,也简化了处理的过程。因为不再需要执行object与实际操作的数据类型之间的转换。
    因此,泛型可以使开发人员最大限度地重用代码,并使得代码更加简单,更加安全。

    泛型类型因类型参数的不同而不同。
    理解泛型类型的一个关键在于,对某个泛型类型的一个版本的引用并不能与同一泛型类型的另一个版本相兼容。

    1.2泛型如何实现类型安全?
    泛型可以自动确保有关的泛型类的所有操作都是类安全的。在处理过程中,泛型避免了手工进行转换和编写类型检查代码的必要。

    1.3泛型类的通用形式:
    class class-name<type-param-list>{}
    声明一个对泛型类的引用的语法:
    class-name<type-arg-list> var-name=new class-name<type-arg-list>(cons-arg-list);
    举例:
    class GenericClass<T, V>
    {
            T data;//declare an object of type T
            V data2;       
            public GenericClass(T obT, V ob2)
            {
                data1 = ob1;
                data2 = ob2;
            }
            public T GetData()
            {
                return this.data;
            }
            public void ShowType()
            {
                Console.WriteLine("The type of T is : {0},The type of V is:{1}",typeof(T),typeof(V));
            }
    }

    在实际应用中,我们往往会对类型T进行一定的约束,以限制应用区域。

    二、类型约束(Constrained types)

    2.1类型约束(Constrained types)
    在指定一个类型参数时,可以指定类型参数必须满足的约束条件。这是通过在指定类型参数时使用where子句来实现的。
    class class-name<type-param> where type-param:constraints{}
    其中constraints是一个逗号分割的约束列表。

    2.2五种约束类型
    (1)可以使用“基类约束”(base class constraint)来指定某个基类必须出现在类型实参中。这种约束是通过指定基类名称来实现的。
    (2)可以使用“接口约束”(interface constraint)来指定某个类型实参必须实现一个或多个接口。这种约束是通过指定接口名称来实现的。
    (3)可以要求类型实参必须提供一个无参数的构造函数,这被称为“构造函数约束”(constructor constraint)。它是通过new()指定的。
    (4)可以通过关键字class指定“引用类型约束”(reference type constraint)来限制某个类型实参必须是引用类型。
    (5)可以通过关键字struct指定“值类型约束”(vlaue type constraint)来限制某个类型实参必须是值类型。
    2.2.1 基类约束
    使用基类约束,我们可以指定某个类型实参必须继承的基类。
    基类约束有两个功能:
    (1)它允许在泛型类中使用由约束指定的基类所定义的成员。例如,可以调用基类的方法或者使用基类的属性。如果没有基类约束,编译器就无法知道某个类型实参拥有哪些成员。通过提供基类约束,编译器将知道所有的类型实参都拥有由指定基类所定义的成员。
    (2)确保类型实参支持指定的基类类型参数。这意味着对于任意给定的基类约束,类型实参必须要么是基类本身,要么是派生于该基类的类,如果试图使用没有继承指定基类的类型实参,就会导致编译错误。
    基类约束使用下面形式的where子句:
    where T:base-class-name
    T是类型参数的名称,base-class-name是基类的名称,这里只能指定一个基类。

    2.2.2 接口约束
    接口约束用于指定某个类型参数必须应用的接口。接口的两个主要功能和基类约束完全一样。
    基本形式 where T:interface-name
    interface-name是接口的名称,可以通过使用由逗号分割的列表来同时指定多个接口。
    如果某个约束同时包含基类和接口,则先指定基类列表,再指定接口列表。

    2.2.3 new()构造函数约束
    new()构造函数约束允许开发人员实例化一个泛型类型的对象。
    一般情况下,我们无法创建一个泛型类型参数的实例。然而,new()约束改变了这种情况,他要求类型参数必须提供一个无参数的构造函数。
    在使用new()约束时,可以通过调用该无参构造函数来创建对象。
    基本形式: where T : new()
    使用new()约束时应注意两点:
    (1)它可以与其他约束一起使用,但是必须位于约束列表的末端。
    (2)new()仅允许开发人员使用无参构造函数来构造一个对象,即使同时存在其他的构造函数。换句话说,不允许给类型参数的构造函数传递实参。

    2.2.4 引用类型和值类型约束
    如果引用类型和值类型之间的差别对于泛型代码非常重要,那么这些约束就非常有用。
    基本形式:
    where T : class
    where T : struct
    若同时存在其他约束的情况下,class或struct必须位于列表的开头。

    另外可以通过 使用约束来建立两个类型参数之间的关系
    例如 class GenericClass2<T, V> where V:T{} -------- 要求V必须继承于T,这种称为裸类型约束(naked type constraint)

    举例:
    class BaseC
    {
            int baseInt;
            string baseStr;
            public void Show()
            {
                Console.WriteLine("Something");
            }
    }
    //基类约束
    class GenericClass3<T> where T : BaseC
    {
            T ob1;
            public void Show()
            {
                ob1.Show();
            }
    }
    //new()构造函数约束
    class GenericClass4<T> where T : new()
    {
            public T ob1;
            public GenericClass4()
            {
                ob1 = new T();
            }
    }

    public interface IBaseService<T>where T:class,new()
    创建一个泛型接口IBaseService,并且这个接口中的<T>只适用于类,且参数T必须具有无参的构造函数

    2.2.5 default 关键字
    泛型代码中的默认关键字(C# 编程指南)
    在泛型类和泛型方法中产生的一个问题是,在预先未知以下情况时,如何将默认值分配给参数化类型 T:

    T 是引用类型还是值类型。

    如果 T 为值类型,则它是数值还是结构。

    给定参数化类型 T 的一个变量 t,只有当 T 为引用类型时,语句 t = null 才有效;只有当 T 为数值类型而不是结构时,语句 t = 0 才能正常使用。解决方案是使用 default 关键字,此关键字对于引用类型会返回空,对于数值类型会返回零。对于结构,此关键字将返回初始化为零或空的每个结构成员,具体取决于这些结构是值类型还是引用类型。

    三、使用泛型类

    在给泛型类传递类型实参的时候,实际创建的是C#中的“封闭构建类型”(closed constructed type)。
    术语“封闭”一词,是指指定了类型实参,因此GenericClass<int>是一个封闭构建类型。
    本质上,泛型类型,例如GenericClass<T>,是一种抽象结构。只有在特定的版本(例如GenericClass<int>)被构建以后才创建了一个实际的类型。
    在C#中术语中,GenericClass<T>之类的构造被称为“开放构建类型”(open constructed type),是因为他没有指定类型实参。
    举例:
    public void Demo1()
    {
           GenericClass<int,string> gen=new GenericClass<int,string>(12,"I am Chinese"); 
           Console.Write("Type: ");
           gen.ShowType();
           Console.WriteLine("The first Data: " + gen.GetData());
    }
    //new()构造函数约束
    public void Demo2()
    {
           GenericClass4<BaseC> demo4 = new GenericClass4<BaseC>();
           demo4.ob1.Show();
    }

    总结:

    ①:default

          在泛型中,要为某个使用泛型的变量初始化值,可是我们需要考虑的是这个泛型可能是引用类型,也可能是值类型,这时我们可以借助default来完成初始化复制。T value = default(T);如果T是引用类型,value = null,如果是T是值类型,value = 0.

    ②:where

                                           约束                                                                  说明

                                    where T:struct                                     使用结构约束,类型T必须是值类型

                                    where T:calss                                       类约束指定,类型T必须是引用类型

                                    where T:IFoo                                            指定类型T必须执行结构IFoo

                                    where T:Foo                                            指定类型T必须派生于基类Foo

                                   where T:new()                                       指定类型T必须有一个默认构造函数 

                                     where T:U                                          类型T派生于泛型类型V(裸类型约束)

       (注:在CRL2.0中,只能为默认构造函数定义约束,不能为其他构造函数定义约束)

     四.泛型继承

     如何实现C#泛型类的继承呢?这里需要满足下面两点中的任何一点即可:

    1、泛型类继承中,父类的类型参数已被实例化,这种情况下子类不一定必须是泛型类;

    2、父类的类型参数没有被实例化,但来源于子类,也就是说父类和子类都是泛型类,并且二者有相同的类型参数;

    //如果这样写的话,显然会报找不到类型T,S的错误   
    public class TestChild : Test< T, S> { }  

    //正确的写法应该是  

    public class TestChild : Test< string, int>{ }  

    public class TestChild< T, S> : Test< T, S> { }

    Csdn上说约束是使用 where 上下文关键字指定的,那么就可以知道T 后面的约束是用来约束T的,也就是说T:后面指定的就是该类型的。

    迷糊:如果where约束了到了一个确定的类型,那么这个泛型还有什么作用呢?

    原来后面的约束类型还是一个范围的,只不过是该范围定的更小了,例如T:类,则这样的范伟指定的就是类型参数必须是引用类型,包括任何类、接口、委托或数组类型,如果除了这几样就是非法的了,可见优点还是大大的,可以输入任何的引用类型同时也确定了范伟,防止输入结果之类的值类型导致错误。可见好处多多。

    下面是csdn中列出的约束类型介绍。

     

    约束

    说明

    T:结构

    类型参数必须是值类型。可以指定除 Nullable 以外的任何值类型。有关更多信息,请参见使用可空类型(C# 编程指南)

    T:类

    类型参数必须是引用类型,包括任何类、接口、委托或数组类型。

    T:new()

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

    T:<基类名>

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

    T:<接口名称>

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

    T:U

    为 T 提供的类型参数必须是为 U 提供的参数或派生自为 U 提供的参数。这称为裸类型约束。

     

    只里面值得注意的地方就是那个T:new(),因为在说明中已经清楚的说明,如果还有其他的约束在一起的话,必须放在最后,这个一定要注意一下。

    在注意的地方就是:在应用 where T : class 约束时,建议不要对类型参数使用 == 和 != 运算符,因为这些运算符仅测试引用同一性而不测试值相等性。即使在用作参数的类型中重载这些运算符也是如此。

     

    可以对同一类型参数应用多个约束,并且约束自身可以是泛型类型,这里面就不得不重提上面的注意了,如果有new(),则一定要放在最后。

     

    通过约束类型参数,可以增加约束类型及其继承层次结构中的所有类型所支持的允许操作和方法调用的数量。

    因此,在设计泛型类或方法时,如果要对泛型成员执行除简单赋值之外的任何操作或调用System.Object 不支持的任何方法,您将需要对该类型参数应用约束。

     

    未绑定的类型参数


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

    ·   不能使用 != 和 == 运算符,因为无法保证具体类型参数能支持这些运算符。

    ·   可以在它们与 System.Object 之间来回转换,或将它们显式转换为任何接口类型。

    ·   可以将它们与 null 进行比较。将未绑定的参数与 null 进行比较时,如果类型参数为值类型,则该比较将始终返回 false。

    · 裸类型约束

    ·             用作约束的泛型类型参数称为裸类型约束。当具有自己的类型参数的成员函数需要将该参数约束为包含类型的类型参数时,裸类型约束很有用,

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

  • 相关阅读:
    全局对象
    公用属性与原型链
    内存图
    JS中数据类型的转换
    for in 遍历
    JS里的数据类型
    Collect~Something else
    Spring Batch学习笔记(一)
    C#之WinForm设置控件居中
    C#之使用AutoUpdater自动更新客户端
  • 原文地址:https://www.cnblogs.com/genesis/p/4950276.html
Copyright © 2020-2023  润新知