6.6 KiB
6.6 KiB
泛型
概念: 泛型就是把==类型==参数化,让一份代码可以适配多种数据类型。 可以理解为:先把类型空出来,等使用时再指定具体类型。
例如:
List<int> nums = new List<int>();
List<string> names = new List<string>();
其中 int 和 string 就是传入泛型的具体类型。
为什么需要泛型
泛型的主要作用:
- 提高代码复用性
- 保证类型安全
- 减少强制类型转换
- 避免不必要的装箱和拆箱
对比 ArrayList:
ArrayList list = new ArrayList();
list.Add(1);
list.Add("hello");
int num = (int)list[0];
ArrayList 内部存的是 object,取值时经常需要强制转换。
使用泛型集合:
List<int> list = new List<int>();
list.Add(1);
int num = list[0];
List<int> 只能存 int,编译器会提前帮我们检查类型错误。
常见泛型形式
泛型可以用于:
- 泛型类
- 泛型接口
- 泛型方法
- 泛型委托
- 泛型集合
常见泛型集合:
List<T>
Dictionary<TKey, TValue>
Queue<T>
Stack<T>
HashSet<T>
T、TKey、TValue 都是泛型类型参数名。
语法
泛型类:
class 类名<T>
{
}
泛型接口:
interface 接口名<T>
{
}
泛型方法:
返回值类型 方法名<T>(T 参数)
{
}
多个泛型类型参数:
class 类名<T1, T2>
{
}
泛型类
泛型类适合用来保存或处理某种不确定类型的数据。
class Box<T>
{
public T Value { get; set; }
public Box(T value)
{
Value = value;
}
public T GetValue()
{
return Value;
}
}
使用:
Box<int> intBox = new Box<int>(10);
Console.WriteLine(intBox.GetValue());
Box<string> stringBox = new Box<string>("hello");
Console.WriteLine(stringBox.GetValue());
理解:
Box<T>定义时不知道T是什么类型Box<int>使用时把T替换成intBox<string>使用时把T替换成string
泛型接口
泛型接口常用于规定一类对象必须提供某种泛型能力。
interface IRepository<T>
{
void Add(T item);
T Get(int index);
}
实现:
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];
}
}
使用:
IRepository<string> repository = new MemoryRepository<string>();
repository.Add("小明");
Console.WriteLine(repository.Get(0));
泛型方法
泛型方法适合处理“逻辑一样,但参数类型不同”的情况。
static void Print<T>(T value)
{
Console.WriteLine(value);
}
使用:
Print<int>(100);
Print<string>("hello");
Print<bool>(true);
很多时候,编译器可以自动推断泛型类型:
Print(100);
Print("hello");
Print(true);
泛型约束
默认情况下,泛型类型 T 可以是任意类型。
如果希望限制 T 必须满足某些条件,可以使用 where 约束。
语法:
class 类名<T> where T : 约束
{
}
常见约束:
| 约束 | 含义 |
|---|---|
where T : class |
T 必须是引用类型 |
where T : struct |
T 必须是值类型 |
where T : new() |
T 必须有无参构造函数 |
where T : 父类名 |
T 必须继承某个父类 |
where T : 接口名 |
T 必须实现某个接口 |
案例:
class Factory<T> where T : new()
{
public T Create()
{
return new T();
}
}
因为加了 where T : new(),所以可以在类中使用 new T()。
多泛型参数
一个类或方法可以同时使用多个泛型参数。
class Pair<TKey, TValue>
{
public TKey Key { get; set; }
public TValue Value { get; set; }
public Pair(TKey key, TValue value)
{
Key = key;
Value = value;
}
}
使用:
Pair<int, string> pair = new Pair<int, string>(1, "小明");
Console.WriteLine(pair.Key);
Console.WriteLine(pair.Value);
default 关键字
泛型中有时不知道 T 的默认值是什么,可以使用 default。
static T GetDefault<T>()
{
return default(T);
}
常见默认值:
| 类型 | 默认值 |
|---|---|
int |
0 |
bool |
false |
| 引用类型 | null |
| 结构体 | 所有字段都是默认值 |
新语法也可以写成:
return default;
完整案例
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];
}
}
}
理解:
DataStore<T>是泛型类IDataStore<T>是泛型接口Print<T>是泛型方法DataStore<int>只能保存整数DataStore<string>只能保存字符串
注意
- 泛型类型参数常用
T表示,但不是固定写法 - 多个泛型参数常写成
TKey、TValue、TItem List<T>比ArrayList更常用,也更安全- 泛型在编译期就能检查类型错误
- 使用泛型可以减少
object带来的强制转换 - 如果需要在泛型中
new T(),必须加where T : new()约束 - 泛型不是“任何类型都乱放”,而是“使用时确定一种类型”
小结
一句话: 泛型就是==把类型当作参数传进去==。
记忆: 普通参数控制“值”。 泛型参数控制“类型”。
最常见写法:
List<int> nums = new List<int>();
Dictionary<string, int> scores = new Dictionary<string, int>();