C#泛型

泛型将类型作为参数。

泛型可以用于方法,类,结构,委托,接口。

泛型方法

语法:

返回类型 方法名称<泛型参数1,泛型参数2>(参数列表)

在返回类型和参数列表的类型中,可以使用泛型参数

泛型参数一般使用T开头。

public static void Print<T>(T obj)
{
    Console.WriteLine(obj.ToString());
}

使用泛型方法

Print<int>(100);
Print<string>("hello");
Print(5+5);

在使用泛型方法时,需要指定泛型类型。

如果参数可以推断处理,可以不指定泛型参数。

程序运行时,即时编译会为不同的泛型参数生产不同的方法。其中值类型是一个类型一个方法,引用类型使用同一方法。

泛型默认值

在泛型中,可以使用default(T)获取默认值。对于引用类型是null,对于值类型,是各自类型的默认值。

泛型委托

delegate 返回类型 方法名称<泛型参数1,泛型参数2>(参数列表)

.NET内置大量的ActionFunc泛型委托,前者代表没有返回值的委托,后者代表有返回值的委托。

通过内置泛型委托,基本不再需要自行定义委托。

泛型类

定义泛型类

语法

[可见性修饰符] [类修饰符] class 类名<泛型参数1,泛型参数2> : 基类,接口1,接口2
{

}
public class List<T>
{
    public void Add(T item);
    public T this[int index]{;get;set;}
}

使用泛型类

List<int> list1 = new List<int>();
list1.Add(100);

List<string> list2 = new List<string>();
list2.Add("OK");

在使用泛型类型时,需要提供类型参数。

在运行时,list1list2是不同的类型,即时编译为不同的泛型参数生成了不同的代码。

没有提供泛型参数的类型是开放类型,List<>类型是开放类型。

提供了泛型参数的类型被称作封闭类型,List<string>,List<int>是封闭类型,封闭类型才可以被实例化。

注意:虽然string继承自object,但是List<string>List<object>之间并没有继承关系。

泛型类的静态成员

为泛型类指定不同的参数,会创建不同的类型,不同参数泛型类的静态成员是独立的。

public class A<T>
{
    public static int i = 10;
}

A<int>.i  = 100;
Console.WriteLine(A<int>.i);
Console.WriteLine(A<double>i);

通过这个特点,我门可以将泛型类作为以类型作为键的,以静态成员作为值的全局字典来使用。

System.Collections.Generic.Comparer<T>.Default是一个典型的例子,提供了类型的比较器。

泛型接口

语法

[可见性修饰符] interface 接口名称<泛型参数1,泛型参数2>
{

}

泛型接口可以被其他接口继承,也可以被类实现。

泛型约束

泛型约束为泛型参数添加了限制。

语法

where T:约束

泛型约束有:

where T:new

要求泛型参数必须提供公共的无参构造函数。这个约束必须放在最后。

public static T CreateInstance<T>() where T:new{
    return new T();
}

where T:接口

要求泛型参数必须实现指定的接口

public static bool Between<T>(T value,T a,T b)where T:IComparable<T>
{
    return value.Compare(a) >=0 && value.Compare(b) <=0;
}

where T:基类

要求泛型类型必须继承指定的基类。 基类不可以是一些特殊类型,如:ValueType,Delegate,Enum

where T:class

要求泛型类型必须是引用类型。

public static bool IsNull<T>(T value) where T:class
{
    return value == null;
}

where T:struct

要求泛型类型必须是结构。 结构不可以是Nullable<T>

where T:U

要求泛型类型必须继承或实现另一个泛型参数。

public static void F<T,U>(T t,U u) where T:U
{

}

where T:unmanged(C#7.3) 要求泛型类型必须是非引用类型,并且任何嵌套级别都不包含引用类型字段。 这个约束不可以和new,struct,class同时使用。 主要是为了更容易在C#中编写低级别的互操作代码。


struct Point
{
    public int X {get;set;}
    public int Y {get;set;}
}

struct Person
{
    public int Age{get;set;}
    public string Name{get;set;}
}

class Program
{
    public static void F<T>() where T:unmanged
    {

    }

    public static void Main()
    {
        F(100);
        F(new Point());
        F(new Person());//错误,Person的Name是引用类型
    }
}

泛型的优点

性能

不使用泛型,集合类型的元素只能通过object来容纳,值类型被添加时需要装箱,访问时需要拆箱。

安全

不使用泛型,集合类型的元素只能通过object来容纳,使用者可以向集合中添加任何类型的元素。

泛型限制了集合元素的类型,使类型的使用更安全。

上面的优点主要体现在集合类型,这也是泛型类主要使用场景。

泛型类型和继承

泛型类型同样可以参与继承。

泛型类可以继承自另一泛型类

class List<T>
{

}

class SuperList<T> :List<T>
{

}

非泛型类型可以继承自泛型类

class List<T>
{

}

class IntList: List<int>
{
}

非泛型类继承自泛型类是一种特化。

如果不需要为子类添加方法,建议使用using定义别名,这样类型一致。

泛型类可以继承自非泛型类

public class Ref
{
    public object Value{get;set}
}

public class Ref<T>:Ref
{
	public new T Value 
	{
        get { return (T)base.Value; }
        set { base.Value = value; }
	}
}

泛型类继承自非泛型类是一种兼容。

泛型接口和泛型委托泛型参数的协变与逆变

通过为泛型参数添加out修饰,说明泛型参数是协变的。协变的参数只能作为返回值。

public delegate T Func<in T>();

public class Program
{
    public static void Main()
    {
        Func<string> getString = ()=>"ok";
        Func<object> getObject = getString;
        object obj = getObject();
    }
}


通过为泛型参数添加`in`说明泛型参数是逆变的。逆变的参数只能作为参数。

```csharp
public delegate void Action<out T>(T arg);

public class Program
{
    public static void Main()
    {
        Action<object> printObject = x => Console.WriteLine(x);
        Action<string> printString = printObject;
        printString("ok");
    }
}

如果愿意,不同的泛型参数可以分别是协变和逆变的。

public delegate TResult Func<in TArg,out TResult>(TArg arg);

泛型的遗憾

泛型不能够以操作符作为泛型约束,不能更大程度的复用代码。

假想的求和和计算平均数

public static T Sum<T>(params T[] items) where T:T+T
{
    T sum = default(T);
    foreach(var item in items)
    {
        sum+=item;
    }
    return sum;
}

public static T Average<T>(params T[] items) where T:T+T where T:T/T
{
    T sum = default(T);
    foreach(var item in items)
    {
        sum+=item;
    }
    return sum/items.Length;
}

public static double Average2<T>(params T[] items) where T:T+T where T:T/double
{
    T sum = default(T);
    foreach(var item in items)
    {
        sum+=item;
    }
    return sum/(double)items.Length);
}