泛型

泛型中包含一个或多个类型参数(type parameters),类型参数以占位符的形式被声明,在运行时指定具体的类型。

泛型类和方法将可重用性、类型安全性和效率结合在一起,这是非泛型类和方法所不能做到的。泛型广泛用于容器(collections)和对容器操作的方法中。

类型参数的约束

where T : struct

类型参数必须是不可为空值类型。

(所有的值类型都有一个公有的无参构造函数,所以此约束不能和new()约束一起使用)

 where T : class 类型参数必须是一个引用类型。此约束可以应用于 class 、interface、delegate和array类型。(在C#8.0或更高版本中的可空上下文中,T必须是不可为空的引用类型。)
where T : class? 类型参数必须是引用类型,可以为Null或不可为Null都行。此约束可以应用于 class 、interface、delegate和array类型。
where T : notnull 类型参数必须是不可为空的类型。(可以是引用类型也可以是值类型)
 where T : unmanaged  类型参数必须是非托管类型,非托管约束隐含struct约束,不能与struct或new()约束组合。
 where T : new()  类型参数必须具有公共无参数构造函数。与其他约束一起使用时,必须最后指定new()约束。New()约束不能与struct和unmanaged约束组合。
 where T :<base class name>  类型参数必须是指定的基类或派生自指定基类的子类。 在 C# 8.0 及更高版本中的可为 null 上下文中,T 必须是从指定基类派生的不可为 null 的引用类型。
 where T :<base class name>?  类型参数必须是指定的基类或派生自指定基类的子类。 在 C# 8.0 及更高版本中的可为 null 上下文中,T 可以是从指定基类派生的可为 null 的引用类型。
 where T :<interface name>   类型参数必须是指定的接口或实现指定接口的类可指定多个接口约束。 约束接口也可以是泛型。可空上下文中,T为非空类型。
 where T :<interface name>?    类型参数必须是指定的接口或实现指定接口的类。可指定多个接口约束。 约束接口也可以是泛型。可空上下文中,T为可空类型。
 where T : U   类型参数必须是U或U的子类。

约束指定了类型参数的功能。声明这些约束意味着可以使用约束类型内的操作和方法调用。如果泛型类或泛型方法中包含System.Object不支持的方法或属性,则必须对类型参数进行约束。约束就是为了告诉编译器,类型参数有哪些属性和方法。官网demo:

public class GenericList<T>
{
    //泛型在嵌套类中同样有效
    private class Node
    {
        // T用于非泛型的构造函数
        public Node(T t)
        {
            next = null;
            data = t;
        }
        private Node next;
        public Node Next
        {
            get { return next; }
            set { next = value; }
        }
        //作为私有字段类型
        private T data;
        // 作为公有属性类型
        public T Data
        {
            get { return data; }
            set { data = value; }
        }
    }
    private Node head;
    public GenericList()
    {
        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;
        }
    }
}

约束多个类型参数和多约束应用与一个类型参数:

class Base { }
class Test<T, U>
    where U : struct
    where T : Base, new()
{ }

而对于无约束的类型参数,只能被当作System.Object去处理,而且不建议使用==和!=运算符,因为不能保证具体类型参数支持这些运算符。但是可以与null进行比较,如是类型参数是值类型,只会返回false。

 协变和逆变

public class People{}//基类
public class Chinese : People{} //子类
static void Main(string[] args)
{
    //错误代码,无法执行隐士转换
    List<People> test = new List<Chinese>();
}

 

  泛型类更像一个类的蓝图(看成是一个新类型),Peole和Chiness有继承关系,但是 List<People>和List<Chinese>没有继承关系。为解决这个问题微软引入逆变和协变的概。

  对于协变(使用out修饰泛型的类型参数)和逆变(使用in修饰泛型的类型参数),官方给出的定义大概是(我自己的理解+总结):协变是实际的返回类型比泛型参数定义的返回类型的派生程度更大,而逆变是传入的实参比泛型定义的参数的派生程度更小。

咋一看,有些懵逼,其实就是将类型参数的里氏替换原则应用到了外层,好像还是有些懵逼。举例说明吧:

IEnumerable<String> strings = new List<String>();
IEnumerable<Object> objects = strings;

List<T>和IEnumrable<T>具有继承关系,String和Objec也具有继承关系,但是IEnumerable<String>和IEnumerable<Object>没有继承关系而逆变和协变将这种继承关系扩展到了外层。

协变和逆变被统一称作“变体”,而且只能应用于接口或委托。变体中只支持引用类型,不支持值类型。

//编译失败,IEnumerable<int>不能隐式转换为IEnumerable<Object>,因为int是值类型
IEnumerable<int> integers = new List<int>();

变体解决了泛型的一个痛点问题:类型参数有继承关系,而泛型类型没有继承关系,泛型与泛型之间不能使用里氏替换:

//无法将List<string>隐士转换为 List<Object>
List<Object> list = new List<string>();

 协变泛型接口

可以使用 out 关键字将泛型类型参数声明为协变。 协变类型必须满足以下条件:

1、协变类型仅用作接口方法的返回类型,不能用作接口方法的参数类型。

 interface ICovariant<out R>
 {
     //OK
     R GetSomething();
     // 编译失败
     //void SetSomething(R sampleArg);
 }

这里有一个例外,如果逆变类型的委托作为接口方法的参数是可以的。

interface ICovariant<out R>
{
    void DoSomething(Action<R> callback);
}

2、不能用作接口方法的泛型约束:

interface ICovariant<out R>
{
    //编译失败,R必须是逆变或没有in 和 out 修饰的不变体
   // void DoSomething<T>() where T : R;
}

逆变泛型接口

可以使用 in 关键字将泛型类型参数声明为逆变。 逆变类型只能用作方法参数的类型,不能用作接口方法的返回类型。 逆变类型还可用于泛型约束。

interface IContravariant<in A>
{
    void SetSomething(A sampleArg);
    void DoSomething<T>() where T : A;
    //编译失败
    // A GetSomething();
}

 

此外,还可以在同一接口中同时支持协变和逆变,但需应用于不同的类型参数:

interface IVariant<out R, in A>
{
    R GetSomething();
    void SetSomething(A sampleArg);
    R GetSetSomethings(A sampleArg);
}

变体泛型委托

public delegate T SampleGenericDelegate<T>();
public static void Test()
{
    //可以为SampleGenericDelegate<String>和SampleGenericDelegate<Object>分配相同的lambda表达式,因为返回值类型存在隐式转换,方法的签名与委托类型相匹配。
    SampleGenericDelegate<String> dString = () => " ";
    SampleGenericDelegate<Object> dObject = () => " ";

    //尽管String继承自Object,但是SampleGenericDelegate<String>不能隐式转换为SampleGenericDelegate<Object>
    // SampleGenericDelegate <Object> dObject = dString;  
}

与变体泛型接口相同,将委托中的泛型参数显式声明为协变或逆变,可以启用泛型委托之间的隐式转换。

 public delegate O SampleGenericDelegate<in I, out O>(I i);
 public static void Test()
 {
     SampleGenericDelegate<Object, string> dString = (object str) => " ";
     SampleGenericDelegate<string, Object> dObject = dString;
 }

但是变体委托不能合并使用(多播委托)。因为多播委托的类型必须完全相同。否则在运行时抛出异常:

Action<object> actObj = x => Console.WriteLine("object: {0}", x);
Action<string> actStr = x => Console.WriteLine("string: {0}", x);

// System.ArgumentException: Delegates must be of the same type.
//  Action<string> actCombine = actStr + actObj;
// actStr += actObj;
// Delegate.Combine(actStr, actObj);

 

posted @ 2021-01-22 19:20  厉致彤  阅读(71)  评论(0编辑  收藏  举报