泛型方法

public static T First<T>(T[] arr)
{
    return arr[0];
}

int n = First(new[] { 1, 2, 3 });        // T = int,推断
string s = First(new[] { "a", "b" });    // T = string
string s2 = First<string>(new[] { "a" }); // 显式指定

泛型类

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

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

var b1 = new Box<int>(42);
var b2 = new Box<string>("hi");

多类型参数

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

    public Pair(TKey k, TValue v) { Key = k; Value = v; }
}

var p = new Pair<string, int>("age", 30);

约束(where)

// T 必须是引用类型
public class Repo<T> where T : class { }

// T 必须是值类型
public class Container<T> where T : struct { }

// T 必须有无参构造函数
public T Create<T>() where T : new() => new T();

// T 必须实现某接口
public class Sorter<T> where T : IComparable<T>
{
    public void Sort(List<T> items) { ... }
}

// T 必须继承某个类
public class AnimalShelter<T> where T : Animal { }

// 多约束(用 , 分隔)
public class X<T> where T : Animal, IComparable<T>, new() { }

约束让编译器知道你能拿 T 做什么——比如 where T : IComparable<T> 后才能调 T.CompareTo(...)

无约束泛型

public class List<T> { }    // 不要求 T 任何东西

只能用 object 上有的操作(.ToString().GetHashCode()== 比引用)。

默认值

public static T? GetOrDefault<T>(T[] arr)
{
    return arr.Length > 0 ? arr[0] : default;
}

default 关键字:

  • 引用类型 → null
  • 数值 → 0
  • bool → false
  • struct → 所有字段 default 化

类型推断

public static T Identity<T>(T x) => x;

Identity(5);        // T = int
Identity("x");      // T = string
Identity<double>(5); // 显式指定(5 会被隐式转 double)

大多数情况下不用写 <T>——编译器从参数推断。

协变 / 逆变

协变(out):子类型可以替父类型:

IEnumerable<string> s = new List<string>();
IEnumerable<object> o = s;    // ✅(string 是 object 的子类型)

逆变(in):父类型可以替子类型:

Action<object> a = (o) => Console.WriteLine(o);
Action<string> b = a;    // ✅(接受 object 的也能接受 string)

只接口和委托上有 in / out,泛型类没有。99% 用户只是消费现有的,不需要自己声明。

泛型 vs 反射 vs dynamic

// 1. 泛型:编译期类型安全 + 零开销
T First<T>(IEnumerable<T> seq) => seq.First();

// 2. 反射:运行期检查,慢、易出错
object First(object seq) { return seq.GetType().GetMethod("First").Invoke(seq, null); }

// 3. dynamic:运行期解析,丧失类型检查
dynamic First2(dynamic seq) => seq.First();

永远首选泛型。反射 / dynamic 是特殊场景兜底。

例:通用结果类型

public class Result<T>
{
    public T? Value { get; }
    public string? Error { get; }
    public bool IsSuccess => Error is null;

    private Result(T? value, string? error) { Value = value; Error = error; }

    public static Result<T> Ok(T value) => new(value, null);
    public static Result<T> Fail(string error) => new(default, error);
}

// 用
public Result<User> GetUser(int id)
{
    var user = db.Find(id);
    if (user is null) return Result<User>.Fail("not found");
    return Result<User>.Ok(user);
}

var r = GetUser(1);
if (r.IsSuccess) Console.WriteLine(r.Value!.Name);
else Console.WriteLine(r.Error);

泛型集合(下一篇细讲)

List<T>            // 数组式
Dictionary<K, V>   // 哈希字典
HashSet<T>         // 集合
Queue<T>           // 队列
Stack<T>           // 栈
LinkedList<T>      // 双向链表

永远用泛型版本——不要碰 ArrayList / Hashtable(.NET Framework 时代的非泛型 API)。

→ 下一篇 集合