目录

C#-协变与逆变.md

1. 核心概念

1.1 什么是变体 (Variance)

变体描述的是:当存在类型继承关系时,由这些类型构造出的泛型类型之间是否也能保持某种兼容关系。

假设 Dog : Animal,那么:

变体类型 关键字 方向 含义 示例
协变 (Covariance) out 子 → 父(保持方向) I<Dog> 可赋值给 I<Animal> IEnumerable<out T>
逆变 (Contravariance) in 父 → 子(逆转方向) I<Animal> 可赋值给 I<Dog> Action<in T>
不变 (Invariance) 不允许转换 List<Dog>List<Animal> 互不兼容 List<T>

1.2 一句话记忆法

out = 输出 = 协变 = 生产者(只读) in = 输入 = 逆变 = 消费者(只写)

1.3 直觉理解

继承关系:  Dog  ──────►  Animal  (Dog 是 Animal 的子类)

协变:      I<Dog>  ────►  I<Animal>    方向相同 ✔  (out)
逆变:      I<Animal> ───►  I<Dog>      方向相反 ✔  (in)
不变:      无法转换                               (默认)

2. 类型安全基础

2.1 为什么需要变体限制

协变和逆变的限制本质上是为了保证类型安全。考虑以下假设场景:

// ⚠️ 假设 List<T> 支持协变(实际不支持)
List<Dog> dogs = new List<Dog>();
List<Animal> animals = dogs;  // 假设允许
animals.Add(new Cat());       // 💥 运行时灾难!dogs 列表里被放入了 Cat

正因为 List<T> 既有读(输出)又有写(输入)操作,所以它必须是不变的

2.2 安全性规则

类型参数位置 允许的变体 原因
仅出现在返回值(输出位置) 协变 out 取出的永远是"至少是 T"的类型,向上转型安全
仅出现在参数(输入位置) 逆变 in 传入的永远是"至多是 T"的类型,接收更宽的类型安全
既出现在输入又出现在输出 不变 无法同时保证两个方向的安全性

3. 泛型接口中的协变与逆变

3.1 协变接口 (out T)

// 定义:T 只能用于输出位置(返回值、只读属性)
public interface IProducer<out T>
{
    T Produce();
    T Current { get; }  // ✔ 只读属性 OK
    
    // void Consume(T item);   ✘ 编译错误!T 不能出现在输入位置
    // T Value { get; set; }   ✘ 编译错误!set 让 T 出现在输入位置
}

使用示例:

public class AnimalShelter : IProducer<Animal>
{
    public Animal Produce() => new Animal();
    public Animal Current => new Animal();
}

public class DogBreeder : IProducer<Dog>
{
    public Dog Produce() => new Dog();
    public Dog Current => new Dog();
}

// 协变:IProducer<Dog> → IProducer<Animal> ✔
IProducer<Dog> dogBreeder = new DogBreeder();
IProducer<Animal> animalProducer = dogBreeder;  // ✔ 合法!

Animal animal = animalProducer.Produce();  // 返回的 Dog 当然也是 Animal

3.2 逆变接口 (in T)

// 定义:T 只能用于输入位置(方法参数)
public interface IConsumer<in T>
{
    void Consume(T item);
    
    // T Produce();          ✘ 编译错误!T 不能出现在输出位置
    // T Current { get; }    ✘ 编译错误!
}

使用示例:

public class AnimalHandler : IConsumer<Animal>
{
    public void Consume(Animal animal)
    {
        Console.WriteLine($"Handling {animal.GetType().Name}");
    }
}

// 逆变:IConsumer<Animal> → IConsumer<Dog> ✔
IConsumer<Animal> animalHandler = new AnimalHandler();
IConsumer<Dog> dogHandler = animalHandler;  // ✔ 合法!

dogHandler.Consume(new Dog());  // AnimalHandler 能处理任何 Animal,当然能处理 Dog

3.3 同时使用协变与逆变

// 一个接口中可以有多个类型参数,各自独立指定变体
public interface IConverter<in TInput, out TOutput>
{
    TOutput Convert(TInput input);
}

public class AnimalToStringConverter : IConverter<Animal, string>
{
    public string Convert(Animal input) => input.ToString();
}

// in Animal → in Dog (逆变), out string → out object (协变)
IConverter<Animal, string> converter = new AnimalToStringConverter();
IConverter<Dog, object> dogConverter = converter;  // ✔ 同时协变+逆变

3.4 接口继承中的变体

// 协变接口可以继承协变接口
public interface IReadOnlyRepository<out T> : IEnumerable<T>
{
    T GetById(int id);
}

// 逆变接口可以继承逆变接口
public interface ISpecializedComparer<in T> : IComparer<T>
{
    // 额外方法...
}

// ⚠️ 变体方向必须一致,以下会编译错误:
// public interface IBad<out T> : IConsumer<T> { }  ✘ IConsumer 需要 in T

4. 泛型委托中的协变与逆变

4.1 委托中的协变(返回值)

public delegate TResult MyFunc<out TResult>();

MyFunc<Dog> getDog = () => new Dog();
MyFunc<Animal> getAnimal = getDog;  // ✔ 协变

Animal a = getAnimal();  // 实际返回 Dog,作为 Animal 完全安全

4.2 委托中的逆变(参数)

public delegate void MyAction<in T>(T obj);

MyAction<Animal> handleAnimal = a => Console.WriteLine(a.Name);
MyAction<Dog> handleDog = handleAnimal;  // ✔ 逆变

handleDog(new Dog());  // 传入 Dog,处理 Animal 的逻辑当然能处理 Dog

4.3 .NET 内置委托的变体签名

// 标准库定义(简化)
public delegate TResult Func<out TResult>();
public delegate TResult Func<in T, out TResult>(T arg);
public delegate TResult Func<in T1, in T2, out TResult>(T1 arg1, T2 arg2);

public delegate void Action<in T>(T obj);
public delegate void Action<in T1, in T2>(T1 arg1, T2 arg2);

public delegate int Comparison<in T>(T x, T y);

public delegate TOutput Converter<in TInput, out TOutput>(TInput input);

实际应用:

Func<Dog> createDog = () => new Dog();
Func<Animal> createAnimal = createDog;  // ✔ out TResult 协变

Action<Animal> printAnimal = a => Console.WriteLine(a);
Action<Dog> printDog = printAnimal;     // ✔ in T 逆变

Func<Animal, string> describeAnimal = a => a.Name;
Func<Dog, object> describeDog = describeAnimal;  // ✔ in + out 同时

5. 数组的协变

5.1 C# 数组协变(历史遗留)

Dog[] dogs = new Dog[3];
Animal[] animals = dogs;  // ✔ 编译通过(数组协变)

animals[0] = new Animal();  // 💥 运行时 ArrayTypeMismatchException!

5.2 为什么数组协变不安全

数组协变是 C# 1.0 时代从 Java 借鉴的设计(为了在没有泛型时支持多态集合操作),它违反了类型安全

// 只读场景是安全的
void PrintAll(Animal[] animals)  // ✔ 只读安全
{
    foreach (var a in animals)
        Console.WriteLine(a);
}

PrintAll(new Dog[] { new Dog(), new Dog() });  // ✔

// 写入场景是危险的
void AddCat(Animal[] animals)  // ✘ 运行时可能崩溃
{
    animals[0] = new Cat();  // 如果实际是 Dog[],则 💥
}

5.3 最佳实践:用泛型接口替代数组协变

// ✘ 不推荐:依赖数组协变
void Process(Animal[] animals) { ... }

// ✔ 推荐:使用协变接口
void Process(IEnumerable<Animal> animals) { ... }      // 只读
void Process(IReadOnlyList<Animal> animals) { ... }     // 只读 + 索引访问

6. .NET 内置协变/逆变接口与委托

6.1 常用协变接口 (out T)

接口 说明
IEnumerable<out T> 可枚举序列(最常用)
IEnumerator<out T> 枚举器
IReadOnlyList<out T> 只读列表
IReadOnlyCollection<out T> 只读集合
IReadOnlyDictionary<TKey, out TValue> 只读字典(仅 Value 协变)
IGrouping<out TKey, out TElement> LINQ 分组
Lazy<out T> 延迟初始化(.NET 4.0+,实际是类但体现了协变思想)

6.2 常用逆变接口 (in T)

接口 说明
IComparer<in T> 比较器
IComparable<in T> 可比较接口
IEqualityComparer<in T> 相等比较器
IEquatable<in T> 可判等(注意:.NET Core 中非变体,需确认版本)
IParsable<in T> .NET 7+ 解析接口

6.3 常用变体委托

委托 变体
Func<out TResult> 协变
Func<in T, out TResult> T 逆变, TResult 协变
Action<in T> 逆变
Predicate<in T> 逆变
Comparison<in T> 逆变
Converter<in TInput, out TOutput> TInput 逆变, TOutput 协变
EventHandler<in TEventArgs> 逆变(.NET 4.5+)

7. 实战设计模式与应用场景

7.1 仓储模式中的协变

public interface IReadRepository<out T> where T : Entity
{
    T GetById(int id);
    IEnumerable<T> GetAll();
}

public interface IWriteRepository<in T> where T : Entity
{
    void Add(T entity);
    void Delete(T entity);
}

// 完整仓储:不变(同时读写)
public interface IRepository<T> : IReadRepository<T>, IWriteRepository<T>
    where T : Entity
{
}

// 使用
public class DogRepository : IRepository<Dog> { /* ... */ }

IReadRepository<Dog> dogRepo = new DogRepository();
IReadRepository<Animal> animalRepo = dogRepo;  // ✔ 协变:可以统一查询

IWriteRepository<Animal> animalWriter = new AnimalRepository();
IWriteRepository<Dog> dogWriter = animalWriter;  // ✔ 逆变:Animal 写入器能写 Dog

7.2 事件/消息处理中的逆变

public interface IEventHandler<in TEvent> where TEvent : IEvent
{
    Task HandleAsync(TEvent @event);
}

public class BaseEventLogger : IEventHandler<IEvent>
{
    public Task HandleAsync(IEvent @event)
    {
        Console.WriteLine($"Event: {@event.GetType().Name}");
        return Task.CompletedTask;
    }
}

// 逆变:IEventHandler<IEvent> → IEventHandler<OrderCreatedEvent>
IEventHandler<IEvent> logger = new BaseEventLogger();
IEventHandler<OrderCreatedEvent> orderHandler = logger;  // ✔

7.3 工厂模式中的协变

public interface IFactory<out T>
{
    T Create();
}

public class DogFactory : IFactory<Dog>
{
    public Dog Create() => new Dog();
}

// 统一管理不同工厂
List<IFactory<Animal>> factories = new()
{
    new DogFactory(),    // ✔ IFactory<Dog> → IFactory<Animal>
    new CatFactory(),    // ✔ IFactory<Cat> → IFactory<Animal>
};

foreach (var factory in factories)
{
    Animal animal = factory.Create();
}

7.4 管道/中间件模式

public interface IValidator<in T>
{
    bool Validate(T item);
    IEnumerable<string> GetErrors(T item);
}

public class AnimalValidator : IValidator<Animal>
{
    public bool Validate(Animal item) => !string.IsNullOrEmpty(item.Name);
    public IEnumerable<string> GetErrors(Animal item)
    {
        if (string.IsNullOrEmpty(item.Name))
            yield return "Name is required";
    }
}

// 逆变:能验证 Animal 的验证器当然能验证 Dog
IValidator<Dog> dogValidator = new AnimalValidator();  // ✔

7.5 LINQ 与协变的协同

public interface ISpecification<in T>
{
    bool IsSatisfiedBy(T entity);
}

public class IsAdultAnimalSpec : ISpecification<Animal>
{
    public bool IsSatisfiedBy(Animal entity) => entity.Age >= 2;
}

// 配合 LINQ 使用
ISpecification<Dog> spec = new IsAdultAnimalSpec();  // ✔ 逆变

IEnumerable<Dog> dogs = GetDogs();
IEnumerable<Animal> animals = dogs;  // ✔ IEnumerable 协变

var adultDogs = dogs.Where(d => spec.IsSatisfiedBy(d));

7.6 依赖注入中的实际应用

// 在 DI 容器中注册
services.AddSingleton<IComparer<Animal>>(new AnimalByNameComparer());

// 由于 IComparer<in T> 是逆变的,理论上可以这样用
// 但注意:大多数 DI 容器不自动支持变体解析,需要手动注册
services.AddSingleton<IComparer<Dog>>(sp => sp.GetRequiredService<IComparer<Animal>>());

8. 常见陷阱与最佳实践

8.1 陷阱一:值类型不支持变体

// ✘ 值类型不参与协变/逆变(因为涉及装箱,不是"真正的"引用转换)
IEnumerable<int> ints = new List<int> { 1, 2, 3 };
// IEnumerable<object> objects = ints;  ✘ 编译错误!

// ✔ 解决方案:显式转换
IEnumerable<object> objects = ints.Cast<object>();
// 或
IEnumerable<object> objects = ints.Select(i => (object)i);

原理:协变和逆变仅适用于引用类型之间的转换。intobject 需要装箱(boxing),不是引用保持(reference-preserving)转换。

8.2 陷阱二:类(class)不支持变体

// ✘ 只有接口和委托支持 in/out 关键字,类不行
// public class MyList<out T> { }  ✘ 编译错误

// ✔ 通过接口暴露变体能力
public interface IReadOnlyBox<out T>
{
    T Value { get; }
}

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

8.3 陷阱三:泛型约束与变体的交互

// out T 可以用于 class/接口约束(协变引用约束)
public interface IProducer<out T> where T : class  // ✔
{
    T Produce();
}

// out T 不能用于具体类型约束的某些场景
// 需要注意约束是否与变体冲突
public interface ISomething<out T> where T : IDisposable  // ✔ 接口约束 OK
{
    T Get();
}

8.4 陷阱四:变体与方法重载

public interface IProcessor<in T>
{
    void Process(T item);
}

public class Processor : IProcessor<Animal>, IProcessor<Dog>
{
    public void Process(Animal item) => Console.WriteLine("Animal");
    public void Process(Dog item) => Console.WriteLine("Dog");
}

var p = new Processor();
IProcessor<Dog> dogProcessor = p;
dogProcessor.Process(new Dog());  // 输出 "Dog"

// 但如果通过逆变赋值:
IProcessor<Animal> animalProcessor = new GeneralProcessor();
IProcessor<Dog> asDogProcessor = animalProcessor;  // 逆变
asDogProcessor.Process(new Dog());  // 调用的是 Process(Animal) 

8.5 陷阱五:协变接口中的泛型方法参数

public interface IMyEnumerable<out T>
{
    IEnumerator<T> GetEnumerator();  // ✔ T 在输出位置(IEnumerator<out T>)

    // ⚠️ 以下情况需要小心
    // bool Contains(T item);  ✘ T 在输入位置,编译错误
    
    // ✔ 可以用泛型方法绕过(但丧失了变体保证)
    bool Contains<TItem>(TItem item);
}

8.6 最佳实践总结

实践 说明
🎯 接口职责分离 将读写拆分为独立接口,分别应用 out / in
🎯 优先使用 IEnumerable<T> 作为方法参数时,比 List<T> 更灵活(协变)
🎯 返回具体类型,接受抽象类型 利用协变返回、逆变参数最大化灵活性
🎯 设计公共 API 时考虑变体 新建泛型接口/委托时,评估是否可以标记 in/out
⚠️ 不要依赖数组协变 IReadOnlyList<T> 替代
⚠️ 注意值类型限制 intstruct 等不参与变体
⚠️ DI 容器不自动解析变体 需要手动注册变体映射

9. 速查表

9.1 语法速查

// 协变(只输出)
interface IXxx<out T>    { T Method(); }
delegate T DXxx<out T>();

// 逆变(只输入)
interface IYyy<in T>     { void Method(T arg); }
delegate void DYyy<in T>(T arg);

// 混合
interface IZzz<in TIn, out TOut> { TOut Convert(TIn input); }
delegate TOut DZzz<in TIn, out TOut>(TIn input);

9.2 判断决策流程

你的泛型类型参数 T 在接口/委托中的位置?
├── 只在返回值 / out 参数 / 只读属性?
│   └── ✔ 使用 out T(协变)
├── 只在方法参数 / 写入位置?
│   └── ✔ 使用 in T(逆变)
├── 同时在输入和输出位置?
│   └── ✘ 不变(默认,不加关键字)
│       └── 考虑拆分为两个接口
└── T 是值类型?
    └── ✘ 变体不适用,无需标记

9.3 赋值方向速查

假设: Dog : Animal : object

协变 (out):  具体 → 抽象(和继承方向一致)
  IEnumerable<Dog>      →  IEnumerable<Animal>      ✔
  IEnumerable<Animal>   →  IEnumerable<object>      ✔
  Func<Dog>             →  Func<Animal>              ✔

逆变 (in):   抽象 → 具体(和继承方向相反)
  Action<Animal>        →  Action<Dog>               ✔
  IComparer<object>     →  IComparer<Animal>         ✔
  IComparer<Animal>     →  IComparer<Dog>            ✔

不变:        无法转换
  List<Dog>             →  List<Animal>              ✘
  List<Animal>          →  List<Dog>                 ✘

9.4 编译器检查清单

在接口或委托上标记 out T 时,编译器确保 T 不出现在

  • ❌ 方法参数类型
  • ❌ 属性的 set 访问器
  • ❌ 逆变泛型类型的类型参数中(嵌套情况)

在接口或委托上标记 in T 时,编译器确保 T 不出现在

  • ❌ 方法返回类型
  • ❌ 属性的 get 访问器
  • ❌ 协变泛型类型的类型参数中(嵌套情况)

附录:完整示例

using System;
using System.Collections.Generic;

// === 类型层次 ===
public class Animal
{
    public string Name { get; set; }
    public int Age { get; set; }
    public override string ToString() => $"{GetType().Name}: {Name}";
}

public class Dog : Animal
{
    public string Breed { get; set; }
}

public class Cat : Animal
{
    public bool IsIndoor { get; set; }
}

// === 协变接口 ===
public interface IReadOnlyRepository<out T>
{
    T GetById(int id);
    IEnumerable<T> GetAll();
}

// === 逆变接口 ===
public interface IEntityValidator<in T>
{
    bool IsValid(T entity);
}

// === 混合变体接口 ===
public interface IMapper<in TSource, out TDestination>
{
    TDestination Map(TSource source);
}

// === 实现 ===
public class DogRepository : IReadOnlyRepository<Dog>
{
    private readonly List<Dog> _dogs = new()
    {
        new Dog { Name = "Buddy", Age = 3, Breed = "Golden Retriever" },
        new Dog { Name = "Max", Age = 5, Breed = "German Shepherd" }
    };

    public Dog GetById(int id) => _dogs[id];
    public IEnumerable<Dog> GetAll() => _dogs;
}

public class AnimalValidator : IEntityValidator<Animal>
{
    public bool IsValid(Animal entity)
        => !string.IsNullOrEmpty(entity.Name) && entity.Age > 0;
}

public class DogToAnimalMapper : IMapper<Dog, Animal>
{
    public Animal Map(Dog source) => new Animal { Name = source.Name, Age = source.Age };
}

// === 使用演示 ===
public class Program
{
    public static void Main()
    {
        // 1. 协变:IReadOnlyRepository<Dog> → IReadOnlyRepository<Animal>
        IReadOnlyRepository<Dog> dogRepo = new DogRepository();
        IReadOnlyRepository<Animal> animalRepo = dogRepo;  // ✔ 协变
        
        foreach (Animal animal in animalRepo.GetAll())
            Console.WriteLine(animal);

        // 2. 逆变:IEntityValidator<Animal> → IEntityValidator<Dog>
        IEntityValidator<Animal> animalValidator = new AnimalValidator();
        IEntityValidator<Dog> dogValidator = animalValidator;  // ✔ 逆变
        
        Console.WriteLine(dogValidator.IsValid(new Dog { Name = "Rex", Age = 2 }));

        // 3. 委托变体
        Func<Dog> createDog = () => new Dog { Name = "Spot" };
        Func<Animal> createAnimal = createDog;  // ✔ 协变

        Action<Animal> logAnimal = a => Console.WriteLine($"Logged: {a}");
        Action<Dog> logDog = logAnimal;  // ✔ 逆变
        logDog(new Dog { Name = "Fido" });

        // 4. IEnumerable 协变(最常见场景)
        List<Dog> dogs = new() { new Dog { Name = "A" }, new Dog { Name = "B" } };
        PrintAnimals(dogs);  // ✔ List<Dog> → IEnumerable<Dog> → IEnumerable<Animal>
    }

    static void PrintAnimals(IEnumerable<Animal> animals)
    {
        foreach (var a in animals)
            Console.WriteLine(a);
    }
}