# 泛型
## 什么是泛型
泛型是一种程序设计语言的特性。泛型是2.0版本C#语言和公共语言运行库(CLR)中新增的一个非常重要的特性。

我们在编程程序时，经常会遇到功能非常相似的模块，只是它们处理的数据不一样。但我们没有办法，只能分别写多个方法来处理不同的数据类型。这个时候，那么问题来了，有没有一种办法，用同一个方法来处理传入不同种类型参数的办法呢？泛型的出现就是专门来解决这个问题的，可以看出，微软还是很贴心的。

泛型是允许延迟编写类或方法中编程元素的数据类型的规范，直到实际在程序中使用它的时候。即，泛型允许你编写一个可以与任何数据类型一起工作的类或方法。

## 为什么要使用泛型
上面简单说了下泛型的作用，还是不太具体，有点抽象，我们通过下面几段代码来看下为什么要使用泛型。代码如下：

```csharp
public class GenericClass
{
    public void ShowInt(int n)
    {
        Console.WriteLine("ShowInt print {0},ShowInt Parament Type Is {1}",n,n.GetType());
    }

    public void ShowDateTime(DateTime dt)
    {
        Console.WriteLine("ShowDateTime print {0},ShowDateTime Parament Type Is {1}", dt, dt.GetType());
    }

    public void ShowPeople(People people)
    {
        Console.WriteLine("ShowPeople print {0},ShowPeople Parament Type Is {1}", people, people.GetType());
    }
}

static void Main(string[] args)
{
    GenericClass generice = new GenericClass();
    generice.ShowInt(11);
    generice.ShowDateTime(DateTime.Now);
    generice.ShowPeople(new People { Id = 11, Name = "Tom" });

    Console.ReadKey();
}
```
 我们可以看出这三个方法，除了传入的参数不同外，其里面实现的功能都是一样的。在1.1版的时候，还没有泛型这个概念，那么怎么办呢。就有人想到了OOP三大特性之一的继承，我们知道，C#语言中，所有类型都源自同一个类型，那就是object。
```csharp
public class GenericClass
{
    public void ShowObj(object obj)
    {
        Console.WriteLine("ShowObj print {0},ShowObj Parament Type Is {1}", obj, obj.GetType());
    }
}
        
static void Main(string[] args)
{
    Console.WriteLine("*****************object调用*********************");
    generice.ShowObj(11);
    generice.ShowObj(DateTime.Now);
    generice.ShowObj(new People { Id = 11, Name = "Tom" });

    Console.ReadKey();
}
 ```

 我们可以看出，目地是达到了。解决了代码的可读性，但是这样又有个不好的地方了，我们这样做实际上是一个装箱拆箱操作，会损耗性能。

终于，微软在2.0的时候发布了泛型。接下来我们用泛型方法来实现该功能。
```csharp
public class GenericClass
{
    public void ShowT<T>(T t)
    {
        Console.WriteLine("ShowObj print {0},ShowObj Parament Type Is {1}", t, t.GetType());
    }
}
        
static void Main(string[] args)
{
    Console.WriteLine("*****************object调用*********************");
    generice.ShowT<int>(11);
    generice.ShowT<DateTime>(DateTime.Now);
    generice.ShowT<People>(new People { Id = 11, Name = "Tom" });

    Console.ReadKey();
}
```
在我们调用泛型方法，编译器进行编译时，会确定传入的参数的类型，从而生成副本方法。这个副本方法与原始方法一法，所以不会有装箱拆箱操作，也就没有损耗性能这回事了。

下面我们一起学习下泛型。

## 类型参数
什么是类型参数呢？`GenericList<T>`中的T就是类型参数，说白了就是在定义泛型对象时的类型占位符。

`GenericList<T>`在具体使用无法按照原样使用，因为T不是一个具体的类型.在使用的时候T必须替换为具体的类型，例如`GenericList<string>`。
## 泛型类
泛型类用于封装不特定于特定数据类型的操作。泛型类最常见用法是用于链接列表、哈希表、堆栈、队列和树等集合，无论存储什么类型的数据，添加和删除项等操作方式基本相同。

语法：
```csharp
public class Generic<T>
{
    public T t;

    public Generic() { }
}

```
通常，创建泛型类从现有具体类开始，然后每次逐个讲类型改为类型参数，直到泛化和可用性达到最佳平衡。

创建泛型类时需要注意以下几点：
- 要将哪些类型泛化为类型参数。
  - 可参数化的类型越多，代码就越灵活，其可重用性就越高
  - 过度泛化会加大其他程序人员阅读代码的难度
- 要将何种约束（如果有）应用到类型参数。

## 泛型接口
泛型接口与泛型类类似，只不过没有具体的实现。

语法：
```csharp
interface IMyGenericInterface<T>
{
}
```

泛型类和接口都可以定义多个类型参数，例如：
```csharp
interface IMyGenericInterface<TKey,TValue>
{
}
```

具体类可以实现封闭式构造接口，例如
```csharp
interface IBaseInterface<T> { }

//泛型继承
class SampleClass<T> : IBaseInterface<T> { }

//具体实现继承
class SampleClass : IBaseInterface<string> { }//如果T有约束,那么string类型必须得满足T的约束
```

## 泛型方法
泛型方法指的就是方法定义中的某些参数的类型不是具体类型，而是类型参数，语法如下：
```csharp
public class GenericClass
{
    public void ShowT<T>(T t)
    {
        Console.WriteLine("ShowT print {0},ShowT Parament Type Is {1}", t, t.GetType());
    }
    }
    static void Main(string[] args)
    {
        Console.WriteLine("*****************泛型方法调用*********************");
        generice.ShowT<int>(11);
        generice.ShowT<DateTime>(DateTime.Now);
        generice.ShowT<People>(new People { Id = 11, Name = "Tom" });

        Console.ReadKey();
    }
}    
```
## 泛型约束
定义泛型类时，可以对代码能够在实例化类时用于类型参数的几种类型施加限制。 如果客户端代码尝试使用约束所不允许的类型来实例化类，则会产生编译时错误。 这些限制称为约束。 通过使用 where 上下文关键字指定约束。 下表列出了六种类型的约束：
|约束|说明|
|-|-|
|where T:struct|类型参数必须是值类型，可以指定除Nullable以外的任意值类型|
|where T:class|类型参数必须是引用类型；这同样适用于所有类、接口、委托或数组类型|
|where T:new()|类型参数必须有公共无参构造函数。与其他约束一起使用时，new()约束必须最后指定|
|where T:<基类名称>|类型参数必须为指定的基类或者派生自指定的基类|
|where T:<接口名称>|类型参数必须是指定的接口或者实现指定的接口。可以指定多个接口约束。约束接口也可以是泛型|
|where T:U|为T提供的类型参数必须是为U提供的参数，或者是派生自为U提供的参数|

where T:struct
```csharp
class MyClass<U>
        where U : struct///约束U参数必须为“值 类型”
 { }

 public void MyMetod<T>(T t)
       where T : struct
 {          
 }
```

where T:Class
```csharp
class MyClass<U>
        where U : class///约束U参数必须为“引用类型”
 { }

 public void MyMetod<T>(T t)
       where T : class
 {          
 }
```

where T:new()
```csharp
class EmployeeList<T> where T : Employee, IEmployee, System.IComparable<T>, new()
{
    // ...
}
```
where T:<基类名>
```csharp
public class Employee{}

public class GenericList<T> where T : Employee
```
where T：<接口名称>
```csharp
/// <summary>
    /// 接口
    /// </summary>
    interface IMyInterface
    {
    }

    /// <summary>
    /// 定义的一个字典类型
    /// </summary>
    /// <typeparam name="TKey"></typeparam>
    /// <typeparam name="TVal"></typeparam>
    class Dictionary<TKey, TVal>
        where TKey : IComparable, IEnumerable
        where TVal : IMyInterface
    {
        public void Add(TKey key, TVal val)
        {
        }
    }
```
where T：U
```csharp
class List<T>
{
    void Add<U>(List<U> items) where U : T {/*...*/}
}
```
以上就是对六种泛型的简单示例，当然泛型约束不仅仅适用于类，接口，对于泛型方法，泛型委托都同样适用
## 泛型委托
委托可以定义它自己的类型参数。 引用泛型委托的代码可以指定类型参数以创建封闭式构造类型，就像实例化泛型类或调用泛型方法一样，如以下示例中所示：
```csharp
class Program8
{
    static void Main(string[] args)
    {
        Del<int> m1 = new Del<int>(Notify);
        m1.Invoke(1111);
        Del<string> m2 = new Del<string>(Notify);
        m2.Invoke("字符串");

        Console.ReadKey();
    }

    public delegate void Del<T>(T item);
    public static void Notify<T>(T t)
    {
        Console.WriteLine("{0} type is {1}", t, t.GetType());
    }
}

```
## 泛型代码中的默认关键字：Default
在泛型类和泛型方法中产生的一个问题是，在预先未知以下情况时，如何将默认值分配给参数化类型 T：
- T 是引用类型还是值类型。
- 如果 T 为值类型，则它是数值还是结构。

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

```csharp
namespace MyGeneric
{
    class Program
    {
        static void Main(string[] args)
        {
            object obj1=GenericToDefault<string>();
            object obj2 = GenericToDefault<int>();
            object obj3 = GenericToDefault<StructDemo>();
            Console.ReadKey();
        }
        public static T GenericToDefault<T>() 
        {
            return default(T);
        }
    }
    public struct StructDemo
    {
        public int Id { get; set; }
        public string Name { get; set; }
    }
}
```