# 泛型

概念：
	泛型就是把==类型==参数化，让一份代码可以适配多种数据类型。
	可以理解为：先把类型空出来，等使用时再指定具体类型。

例如：
```csharp
List<int> nums = new List<int>();
List<string> names = new List<string>();
```

其中 `int` 和 `string` 就是传入泛型的具体类型。

----
# 为什么需要泛型

泛型的主要作用：
1. 提高代码复用性
2. 保证类型安全
3. 减少强制类型转换
4. 避免不必要的装箱和拆箱

对比 `ArrayList`：
```csharp
ArrayList list = new ArrayList();
list.Add(1);
list.Add("hello");

int num = (int)list[0];
```

`ArrayList` 内部存的是 `object`，取值时经常需要强制转换。

使用泛型集合：
```csharp
List<int> list = new List<int>();
list.Add(1);

int num = list[0];
```

`List<int>` 只能存 `int`，编译器会提前帮我们检查类型错误。

----
# 常见泛型形式

泛型可以用于：
1. 泛型类
2. 泛型接口
3. 泛型方法
4. 泛型委托
5. 泛型集合

常见泛型集合：
```csharp
List<T>
Dictionary<TKey, TValue>
Queue<T>
Stack<T>
HashSet<T>
```

`T`、`TKey`、`TValue` 都是泛型类型参数名。

----
# 语法

泛型类：
```csharp
class 类名<T>
{
}
```

泛型接口：
```csharp
interface 接口名<T>
{
}
```

泛型方法：
```csharp
返回值类型 方法名<T>(T 参数)
{
}
```

多个泛型类型参数：
```csharp
class 类名<T1, T2>
{
}
```

----
# 泛型类

泛型类适合用来保存或处理某种不确定类型的数据。

```csharp
class Box<T>
{
    public T Value { get; set; }

    public Box(T value)
    {
        Value = value;
    }

    public T GetValue()
    {
        return Value;
    }
}
```

使用：
```csharp
Box<int> intBox = new Box<int>(10);
Console.WriteLine(intBox.GetValue());

Box<string> stringBox = new Box<string>("hello");
Console.WriteLine(stringBox.GetValue());
```

理解：
1. `Box<T>` 定义时不知道 `T` 是什么类型
2. `Box<int>` 使用时把 `T` 替换成 `int`
3. `Box<string>` 使用时把 `T` 替换成 `string`

----
# 泛型接口

泛型接口常用于规定一类对象必须提供某种泛型能力。

```csharp
interface IRepository<T>
{
    void Add(T item);
    T Get(int index);
}
```

实现：
```csharp
class MemoryRepository<T> : IRepository<T>
{
    private List<T> items = new List<T>();

    public void Add(T item)
    {
        items.Add(item);
    }

    public T Get(int index)
    {
        return items[index];
    }
}
```

使用：
```csharp
IRepository<string> repository = new MemoryRepository<string>();

repository.Add("小明");
Console.WriteLine(repository.Get(0));
```

----
# 泛型方法

泛型方法适合处理“逻辑一样，但参数类型不同”的情况。

```csharp
static void Print<T>(T value)
{
    Console.WriteLine(value);
}
```

使用：
```csharp
Print<int>(100);
Print<string>("hello");
Print<bool>(true);
```

很多时候，编译器可以自动推断泛型类型：
```csharp
Print(100);
Print("hello");
Print(true);
```

----
# 泛型约束

默认情况下，泛型类型 `T` 可以是任意类型。

如果希望限制 `T` 必须满足某些条件，可以使用 `where` 约束。

语法：
```csharp
class 类名<T> where T : 约束
{
}
```

常见约束：

| 约束 | 含义 |
| --- | --- |
| `where T : class` | `T` 必须是引用类型 |
| `where T : struct` | `T` 必须是值类型 |
| `where T : new()` | `T` 必须有无参构造函数 |
| `where T : 父类名` | `T` 必须继承某个父类 |
| `where T : 接口名` | `T` 必须实现某个接口 |

案例：
```csharp
class Factory<T> where T : new()
{
    public T Create()
    {
        return new T();
    }
}
```

因为加了 `where T : new()`，所以可以在类中使用 `new T()`。

----
# 多泛型参数

一个类或方法可以同时使用多个泛型参数。

```csharp
class Pair<TKey, TValue>
{
    public TKey Key { get; set; }
    public TValue Value { get; set; }

    public Pair(TKey key, TValue value)
    {
        Key = key;
        Value = value;
    }
}
```

使用：
```csharp
Pair<int, string> pair = new Pair<int, string>(1, "小明");

Console.WriteLine(pair.Key);
Console.WriteLine(pair.Value);
```

----
# default 关键字

泛型中有时不知道 `T` 的默认值是什么，可以使用 `default`。

```csharp
static T GetDefault<T>()
{
    return default(T);
}
```

常见默认值：

| 类型 | 默认值 |
| --- | --- |
| `int` | `0` |
| `bool` | `false` |
| 引用类型 | `null` |
| 结构体 | 所有字段都是默认值 |

新语法也可以写成：
```csharp
return default;
```

----
# 完整案例

```csharp
using System;
using System.Collections.Generic;

namespace GenericsDemo
{
    internal class Program
    {
        static void Main(string[] args)
        {
            DataStore<int> intStore = new DataStore<int>();
            intStore.Add(10);
            intStore.Add(20);

            Console.WriteLine(intStore.Get(0));
            Console.WriteLine(intStore.Get(1));

            DataStore<string> stringStore = new DataStore<string>();
            stringStore.Add("hello");
            stringStore.Add("world");

            Console.WriteLine(stringStore.Get(0));
            Console.WriteLine(stringStore.Get(1));

            Print(123);
            Print("泛型方法");
        }

        static void Print<T>(T value)
        {
            Console.WriteLine(value);
        }
    }

    interface IDataStore<T>
    {
        void Add(T value);
        T Get(int index);
    }

    class DataStore<T> : IDataStore<T>
    {
        private List<T> values = new List<T>();

        public void Add(T value)
        {
            values.Add(value);
        }

        public T Get(int index)
        {
            return values[index];
        }
    }
}
```

理解：
1. `DataStore<T>` 是泛型类
2. `IDataStore<T>` 是泛型接口
3. `Print<T>` 是泛型方法
4. `DataStore<int>` 只能保存整数
5. `DataStore<string>` 只能保存字符串

----
# 注意

1. 泛型类型参数常用 `T` 表示，但不是固定写法
2. 多个泛型参数常写成 `TKey`、`TValue`、`TItem`
3. `List<T>` 比 `ArrayList` 更常用，也更安全
4. 泛型在编译期就能检查类型错误
5. 使用泛型可以减少 `object` 带来的强制转换
6. 如果需要在泛型中 `new T()`，必须加 `where T : new()` 约束
7. 泛型不是“任何类型都乱放”，而是“使用时确定一种类型”

----
# 小结

一句话：
	泛型就是==把类型当作参数传进去==。

记忆：
	普通参数控制“值”。
	泛型参数控制“类型”。

最常见写法：
```csharp
List<int> nums = new List<int>();
Dictionary<string, int> scores = new Dictionary<string, int>();
```
