C# 泛型(Generic)

泛型类、泛型方法、约束、协变逆变、以及默认值。

Intermediate
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
ConstraintMeaning
where T : struct值类型
where T : class引用类型
where T : notnull不可为 null
where T : new()有无参构造
where T : BaseClass继承自某类
where T : IInterface实现某接口
where T : UT 派生自 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
abc

Covariance: 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
False

default(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

AspectList<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.

Friends & Partners