C# 泛型(Generic)
泛型类、泛型方法、约束、协变逆变、以及默认值。
Key Points泛型在运行时保留类型信息(具体化),不像 Java 那样擦除。
泛型类
C#
class Box<T>
{
public T Value { get; set; }
public Box(T value) => Value = value;
public string Describe() => $"Box<{typeof(T).Name}> = {Value}";
}
var b1 = new Box<int>(42);
var b2 = new Box<string>("hello");
Console.WriteLine(b1.Describe());
Console.WriteLine(b2.Describe());Output
Box<Int32> = 42
Box<String> = hello泛型方法
C#
static T Max<T>(T a, T b) where T : IComparable<T>
{
return a.CompareTo(b) >= 0 ? a : b;
}
Console.WriteLine(Max(3, 7));
Console.WriteLine(Max("apple", "banana"));
static void Swap<T>(ref T a, ref T b)
{
(a, b) = (b, a);
}
int x = 1, y = 2;
Swap(ref x, ref y);
Console.WriteLine($"{x} {y}");
// 泛型工厂
static List<T> Repeat<T>(T value, int n)
{
var list = new List<T>(n);
for (int i = 0; i < n; i++) list.Add(value);
return list;
}
Console.WriteLine(string.Join(",", Repeat("x", 3)));Output
7
banana
2 1
x,x,x约束
C#
class Repository<T> where T : class, new()
{
private readonly List<T> _items = new();
public T Create()
{
var item = new T(); // 需要 new() 约束
_items.Add(item);
return item;
}
public int Count => _items.Count;
}
class Item { public string Name { get; set; } = ""; }
var repo = new Repository<Item>();
repo.Create();
Console.WriteLine(repo.Count);
// 多个约束
static void Show<T>(T value) where T : struct, IComparable<T>
{
Console.WriteLine($"{typeof(T).Name} {value}");
}
Show(42);Output
1
Int32 42| Constraint | Meaning |
|---|---|
where T : struct | 值类型 |
where T : class | 引用类型 |
where T : notnull | 不可为 null |
where T : new() | 有无参构造 |
where T : BaseClass | 继承自某类 |
where T : IInterface | 实现某接口 |
where T : U | T 派生自 U |
where T : unmanaged | 非托管类型(可用于指针) |
Constraint order matters:
class/struct first, new() last. Wrong order fails to compile with a not-very-obvious message.协变与逆变
C#
// out:协变,只能作为输出(返回值)
IEnumerable<string> strings = new List<string> { "a", "b" };
IEnumerable<object> objects = strings; // string → object,安全
Console.WriteLine(objects.Count());
// in:逆变,只能作为输入(参数)
Action<object> printObj = o => Console.WriteLine(o);
Action<string> printStr = printObj; // object → string,安全
printStr("hello");
// Func<in T, out TResult>
Func<object, string> toStr = o => o.ToString() ?? "";
Func<string, object> wider = toStr;
Console.WriteLine(wider("abc"));Output
2
hello
abcCovariance: a List<string> is an IEnumerable<object> since every string is an object. Contravariance: a method handling object can certainly handle string.
默认值
C#
static T? GetDefault<T>() => default;
Console.WriteLine(GetDefault<int>());
Console.WriteLine(GetDefault<int?>() == null);
Console.WriteLine(GetDefault<string>() == null);
static bool IsDefault<T>(T value) => EqualityComparer<T>.Default.Equals(value, default!);
Console.WriteLine(IsDefault(0));
Console.WriteLine(IsDefault(""));
Console.WriteLine(IsDefault(1));Output
0
True
True
True
True
Falsedefault(T) is zero for value types and null for references. Compare generic values with EqualityComparer<T>.Default — == will not compile on an unconstrained T.
泛型 vs object
| Aspect | List<object> | List<T> |
|---|---|---|
| 类型安全 | 运行时才发现错 | 编译期检查 |
| 装箱 | 值类型要装箱 | 无 |
| 性能 | 慢(装箱+拆箱+转换) | 快(专用代码) |
| 可读性 | 需强转 | 直接使用 |
C# generics are reified:
List<int> and List<string> are distinct runtime types with their own code. Java erases types to a single List. Hence no boxing and typeof(T) works.泛型类
C#
class Box<T>
{
public T Value { get; set; }
public Box(T value) => Value = value;
public string Describe() => $"Box<{typeof(T).Name}> = {Value}";
}
var b1 = new Box<int>(42);
var b2 = new Box<string>("hello");
Console.WriteLine(b1.Describe());
Console.WriteLine(b2.Describe());Output
Box<Int32> = 42
Box<String> = hello泛型方法
C#
static T Max<T>(T a, T b) where T : IComparable<T>
{
return a.CompareTo(b) >= 0 ? a : b;
}
Console.WriteLine(Max(3, 7));
Console.WriteLine(Max("apple", "banana"));
static void Swap<T>(ref T a, ref T b)
{
(a, b) = (b, a);
}
int x = 1, y = 2;
Swap(ref x, ref y);
Console.WriteLine($"{x} {y}");
// 泛型工厂
static List<T> Repeat<T>(T value, int n)
{
var list = new List<T>(n);
for (int i = 0; i < n; i++) list.Add(value);
return list;
}
Console.WriteLine(string.Join(",", Repeat("x", 3)));Output
7
banana
2 1
x,x,x约束
C#
class Repository<T> where T : class, new()
{
private readonly List<T> _items = new();
public T Create()
{
var item = new T(); // 需要 new() 约束
_items.Add(item);
return item;
}
public int Count => _items.Count;
}
class Item { public string Name { get; set; } = ""; }
var repo = new Repository<Item>();
repo.Create();
Console.WriteLine(repo.Count);
// 多个约束
static void Show<T>(T value) where T : struct, IComparable<T>
{
Console.WriteLine($"{typeof(T).Name} {value}");
}
Show(42);Output
1
Int32 42| 约束 | 含义 |
|---|---|
where T : struct | 值类型 |
where T : class | 引用类型 |
where T : notnull | 不可为 null |
where T : new() | 有无参构造 |
where T : BaseClass | 继承自某类 |
where T : IInterface | 实现某接口 |
where T : U | T 派生自 U |
where T : unmanaged | 非托管类型(可用于指针) |
约束的顺序有要求:
class/struct 必须在前,new() 必须在最后。写错顺序编译报错,但错误信息不太直观。协变与逆变
C#
// out:协变,只能作为输出(返回值)
IEnumerable<string> strings = new List<string> { "a", "b" };
IEnumerable<object> objects = strings; // string → object,安全
Console.WriteLine(objects.Count());
// in:逆变,只能作为输入(参数)
Action<object> printObj = o => Console.WriteLine(o);
Action<string> printStr = printObj; // object → string,安全
printStr("hello");
// Func<in T, out TResult>
Func<object, string> toStr = o => o.ToString() ?? "";
Func<string, object> wider = toStr;
Console.WriteLine(wider("abc"));Output
2
hello
abc协变的直觉:List<string> 是 IEnumerable<object> 的「一种」,因为取出来的 string 也是 object。逆变的直觉:能处理 object 的方法当然也能处理 string。
默认值
C#
static T? GetDefault<T>() => default;
Console.WriteLine(GetDefault<int>());
Console.WriteLine(GetDefault<int?>() == null);
Console.WriteLine(GetDefault<string>() == null);
static bool IsDefault<T>(T value) => EqualityComparer<T>.Default.Equals(value, default!);
Console.WriteLine(IsDefault(0));
Console.WriteLine(IsDefault(""));
Console.WriteLine(IsDefault(1));Output
0
True
True
True
True
Falsedefault(T) 对值类型是零值,对引用类型是 null。比较泛型值要用 EqualityComparer<T>.Default —— 直接用 == 在无约束的 T 上编译不过。
泛型 vs object
| 对比项 | List<object> | List<T> |
|---|---|---|
| 类型安全 | 运行时才发现错 | 编译期检查 |
| 装箱 | 值类型要装箱 | 无 |
| 性能 | 慢(装箱+拆箱+转换) | 快(专用代码) |
| 可读性 | 需强转 | 直接使用 |
C# 泛型是运行时具体化的:
List<int> 和 List<string> 在运行时是不同类型,各有自己的代码。Java 是类型擦除,两者都变成 List。所以 C# 泛型没有装箱开销,也能用 typeof(T)。