泛型的作用

跨类型可复用的代码:继承 和 泛型。
其中继承通过基类来代表代码的复用性,而泛型使用占位符(类型的占位符)
继承 → 基类
泛型 → 带有“(类型)占位符”的”模板“

泛型的类型(GENERIC TYPES)

泛型会声明类型参数 - 泛型的消费者需要提供类型参数(argument)来把占位符类型填充上。

  1. public class Stack<T>
  2. {
  3. int position;
  4. T[] data = new T[100];
  5. public void Push(T obj) => data[position++] = obj;
  6. public T Pop() => data[--position];
  7. }
  8. var stack = new Stack<int>();
  9. stack.Push(5);
  10. stack.Push(10);
  11. int x = stack.Pop(); // x is 10
  12. int y = stack.Pop(); // x is 5
  13. // 相当于下面
  14. public class ###
  15. {
  16. int position;
  17. int[] data = new int[100];
  18. public void Push(int obj) =>data[position++] = obj;
  19. public int Pop() => data[--position];
  20. }

占位符中的T可以使任何合规的输入

OPEN TYPE & CLOSED TYPE

Stack Open Type (开放类型)
Stack Closed Type (封闭类型)
在运行时,所有的泛型类型实例都是封闭的(占位符类型已经被填充了)

  1. var stack = new Stack<T>(); //不合法的,因为占位符类型没有被填充
  2. public class Stack<T> //合法的
  3. {
  4. ..
  5. public Stack<T> Clone()
  6. {
  7. Stack<T> clone = new Stack<T>();
  8. //..
  9. }
  10. }

为什么泛型会出现

  1. public class ObjectStack
  2. {
  3. int position;
  4. object[] data = new object[100];
  5. public void Push(int obj) =>data[position++] = obj;
  6. public object Pop() => data[--position];
  7. }

为了可以写出适应不同类型的代码,如果没有,我们要实现的话就要实现不同类型的Stack,比如int,Long,double.也可以直接写object类型,但是就会出现以下情况。
需要装箱和向下转换,这种转换在编译时无法进行检查

  1. satck.Push("s";) //不同的类型,但是没有报错
  2. int i = (int)stack.Pop(); //向下装换 运行时错误,因为他不是int类型

各种装箱拆箱操作也会影响性能

泛型方法

  • 泛型方法在方法签名内也可以声明类型参数

    1. static void Swap<T>(ref T a, ref T b)
    2. {
    3. T temp = a;
    4. a = b;
    5. b = temp;
    6. }
    7. static void Main(string[] args)
    8. {
    9. int x = 5;
    10. int y = 10;
    11. Swap(ref x, ref y);
    12. Console.WriteLine($"{x},{y}"); // Input 10,5
    13. }
  • 如果发生歧义,不知道类型的类型的话可以swap(ref x, ref y)这样写

  • 在方形类型里面的方法,除非也引入了类型参数(type parameters),否则是不会归为泛型方法的。
    1. public class Stack<T>
    2. {
    3. int position;
    4. T[] data = new T[100];
    5. public void Push(T obj) => data[position++] = obj;
    6. //这引用的T,是在泛型类型那声明的,而不是方法引入的,就是没有是用<>来引入其它的类型参数(占位符),所以说这个Pop()方法它不是泛型方法,只不过是泛型类型里面的一个普通方法
    7. public T Pop() => data[--position];
    8. }
    只有类型和方法可以引入类型参数,属性、索引器、实践、字段、构造函数、操作符等等都不可以声明类型参数。但是他们可以使用他们所在的泛型类型的类型参数。
    1. public T this[int index] => data[index];
    2. public Stack<T>() {} //构造函数引入一个类型参数是不合理的

声明类型参数

在声明class、struct、internal、delegate的时候可以引用类型参数(Type parameters)。
其它的例如属性,就不可以引入类型参数,但是可以使用类型参数。

  1. public struct Nullable<T>
  2. {
  3. public T Value {get;} //可以使用
  4. }

泛型类型/泛型方法可以有多个类型参数:

class Dictionary<TKey,Tvalue>{..}
Dictionary<int,string> myDic = new Dictionary<int,string>();
var myDic = new Dictionary<int,string>();

泛型类型/泛型方法的名称可以被重载,条件是参数类型的个数不同:

class A{}
class A<T>{}
class A<T1,T2>{}

按约定,泛型类型/泛型方法,如果只有一个类型参数,那么就叫T。
当使用多个类型参数的时候,每个类型参数都使用T作为前缀,随后跟着具有描述性的一个名字。
..

TYPEOF 与未绑定的泛型类型

开放的泛型类型在编译后就编程了封闭的泛型类型。
但是如果作为Type对象,那么未绑定的泛型类型在运行时还是可以存在的。
(只能通过typeof操作符来实现)

class A<T>{}
class A<T1,T2>{}

Type a1 = Typeof(A<>)  //联合类型,注意没有类型参数
Type a2 = Typeof(A<,>) //使用逗号表示多个类型参数

开放的泛型类型,通常与反射的API一起来使用的

Type a3 = typeof(A<int,int>);
class B<T> 
{ 
    void x()
    { 
        Type t =typeof(T);
    }
}

泛型的默认值

使用default关键字来获取泛型类型参数的默认值。

static void Zap<T>(T[] array)
{
    for(int i =0; i< array.Length; i++)
        array[i] = default(T);
}