协变(收银员)逆变(外卖员)
协变(收银员)逆变(外卖员)

🌟通俗易懂讲协变与逆变
很多人第一次遇到 协变 (Covariance) 和 逆变 (Contravariance) 都觉得绕口,其实它们的本质就像是 “超市收银台” 与 “外卖送餐” 的区别。让我用简单的比喻和直观类比帮你理解这两个概念! 🚀
🧩 1️⃣ 协变 (Covariance):”我买东西,我不在乎谁卖,只要货对就行”
定义:协变表示你可以使用 返回值类型更具体(子类) 的方法,而不必严格要求返回值是基类类型。
✅ 现实比喻:
你去超市买饮料,只关心买到饮料就行,无论是果汁、可乐,还是矿泉水(都是饮料的子类),你都能接受。
- 我需要的是“饮料” (基类)
- 给我“果汁” (子类) 也可以 —— 这就是协变!
💻 C# 示例(协变 out):
xxxxxxxxxx
// 基类与子类
public class Beverage { }
public class Juice : Beverage { }
// 协变接口 (只生产数据)
public interface IProducer<out T>
{
T Produce();
}
// 子类生产器
public class JuiceProducer : IProducer<Juice>
{
public Juice Produce() => new Juice();
}
// 使用协变
IProducer<Beverage> beverageProducer = new JuiceProducer(); // JuiceProducer 赋给IProducer<Beverage>
Beverage drink = beverageProducer.Produce();
Debug.Log(drink.GetType().Name); // 输出 "Juice"
👉 解释:
IProducer<out T>只提供数据 (只出不进),因此可以 用子类代替父类。- 饮料生产商 (
IProducer<Beverage>) 协变地接受 果汁厂 (JuiceProducer) 提供的果汁。
📝 协变口诀:
“只出不进,子类能行。” (只生产数据,子类可以赋值给父类接口)
🧩 2️⃣ 逆变 (Contravariance):”我只要送餐,你吃啥都行”
定义:逆变表示你可以使用 参数类型更通用(父类) 的方法,而不必严格要求参数是子类类型。
✅ 现实比喻:
你是外卖小哥,只管送餐到门口,不管顾客点的是 “披萨”、“汉堡” 还是 “沙拉”,统统帮他们送。
- 我是“送餐员” (父类)
- 不管披萨还是沙拉(子类),我都能送 —— 这就是逆变!
💻 C# 示例(逆变 in):
xxxxxxxxxx
// 父类与子类
public class Food { }
public class Pizza : Food { }
// 逆变接口 (只消费数据)
public interface IDelivery<in T>
{
void Deliver(T item);
}
// 食物配送员
public class FoodDelivery : IDelivery<Food>
{
public void Deliver(Food item) => Debug.Log($"送达 {item.GetType().Name}");
}
// 使用逆变
IDelivery<Pizza> pizzaDelivery = new FoodDelivery(); // FoodDelivery 赋给 IDelivery<Pizza>
pizzaDelivery.Deliver(new Pizza());
👉 解释:
IDelivery<in T>只接收数据 (只进不出),因此可以 用父类代替子类。- 披萨专送 (
IDelivery<Pizza>) 逆变地接受 通用外卖员 (FoodDelivery),因为外卖员送什么都行!
📝 逆变口诀:
“只进不出,父类开路。” (只消费数据,父类可以赋值给子类接口)
🌟 3️⃣ 协变与逆变口诀总结:
| 特性 | 关键字 | 口诀 | 直观理解 |
|---|---|---|---|
| 协变 | out |
“只出不进,子类能行” | 子类替代父类 (输出) |
| 逆变 | in |
“只进不出,父类开路” | 父类替代子类 (输入) |
🎨 4️⃣ 用现实角色记忆协变与逆变:
| 特性 | 现实角色 | 编程含义 |
|---|---|---|
| 协变 | 供应商 (Producer) | “卖饮料” → 协变 out |
| 逆变 | 快递员 (Consumer) | “送餐” → 逆变 in |
🚀 5️⃣ 总结与应用建议
-
🚩 协变 (out) 适合 “只生产数据” 的角色,如:
IEnumerable<T>(只读集合)Func<T>(返回结果)- 数据流或生产者模式
-
🚩 逆变 (in) 适合 “只消费数据” 的角色,如:
IComparer<T>(比较器)Action<T>(只接收参数)- 事件监听或消费者模式
🎯 1️⃣ IEnumera/ble 与协变:多态集合传递
协变 (out) 在集合接口中尤为重要,如 IEnumerable<T> 允许子类型集合被赋值给父类型集合。
xxxxxxxxxx
namespace System.Collections.Generic
{
public interface IEnumerable<out T> : IEnumerable
{
IEnumerator<T> GetEnumerator();
}
}
✅ 案例:动物与猫的集合多态性
xxxxxxxxxx
public class Animal { public string Name { get; set; } }
public class Cat : Animal { public string Breed { get; set; } }
public static void PrintAnimals(IEnumerable<Animal> animals)
{
foreach (var animal in animals)
{
Console.WriteLine($"Animal: {animal.Name}");
}
}
// 协变特性允许子类型集合赋值给父类型集合
IEnumerable<Cat> cats = new List<Cat>
{
new Cat { Name = "Tom", Breed = "Persian" },
new Cat { Name = "Jerry", Breed = "Siamese" }
};
PrintAnimals(cats); // IEnumerable<Cat> -> IEnumerable<Animal>
// 输出:
// Animal: Tom
// Animal: Jerry
✨ 解读:
IEnumerable<out T>使用了 协变 (out),只生产数据(返回值),因此支持多态集合。- 在实际项目中常见于 LINQ 查询链式操作、仓储 (Repository) 层设计 中。
🎯 2️⃣ IComparer 与逆变:通用排序器
逆变 (in) 常用于比较器或过滤器等 “只消费” 输入数据的接口,如 IComparer<T>。
public interface IComparer<in T>
{
int Compare(T x, T y);
}
✅ 案例:订单排序系统
xxxxxxxxxx
public class Order { public int Id { get; set; } }
public class SpecialOrder : Order { public string SpecialNote { get; set; } }
// 订单比较器
public class OrderComparer : IComparer<Order>
{
public int Compare(Order x, Order y) => x.Id.CompareTo(y.Id);
}
public static void SortOrders<T>(List<T> orders, IComparer<T> comparer) where T : Order
{
orders.Sort(comparer);
foreach (var order in orders)
{
Console.WriteLine($"Order ID: {order.Id}");
}
}
// 使用 IComparer<Order> 比较 SpecialOrder
var orders = new List<SpecialOrder>
{
new SpecialOrder { Id = 102 },
new SpecialOrder { Id = 101 }
};
SortOrders(orders, new OrderComparer());
// 输出:
// Order ID: 101
// Order ID: 102
✨ 解读:
IComparer<in T>使用了 逆变 (in),因此子类集合也能被父类比较器处理。- 常应用于 排序算法、搜索算法、集合操作 (如
List<T>.Sort()) 中。
🎯 3️⃣ 多态委托:协变与逆变联合应用
协变与逆变在 委托 (Delegate) 中灵活搭配,使方法分派更加多态。
✅ 案例:泛型委托中的协变与逆变
//泛型委托定义
public delegate TResult Transformer<in TInput, out TResult>(TInput input);
// 方法实现
public static string ProcessAnimal(Animal animal) => $"Animal: {animal.Name}";
public static string ProcessCat(Cat cat) => $"Cat Breed: {cat.Breed}";
// 协变 (返回类型):子类方法作为父类委托实现
Transformer<Cat, string> catTransformer = ProcessCat;
Transformer<Animal, string> animalTransformer = catTransformer; // 协变 (out)
// 逆变 (参数类型):父类方法作为子类委托实现
Transformer<Animal, string> animalProcessor = ProcessAnimal;
Transformer<Cat, string> catProcessor = animalProcessor; // 逆变 (in)
// 测试
Console.WriteLine(animalTransformer(new Cat { Name = "Whiskers", Breed = "Maine Coon" }));
Console.WriteLine(catProcessor(new Cat { Name = "Luna", Breed = "Bengal" }));
// 输出:
// Cat Breed: Maine Coon
// Animal: Luna
✨ 解读:
- 协变 (
out):子类委托 (Transformer<Cat,string>) 赋值给父类委托 (Transformer<Animal,string>) - 逆变 (
in):父类委托 (Transformer<Animal,string>) 赋值给子类委托 (Transformer<Cat,string>) - 常用于 事件回调、策略模式 (Strategy Pattern)、工厂模式 (Factory Pattern) 等场景。
🏆 协变与逆变在设计模式中的角色
| 设计模式 | 协变/逆变角色 | 应用示例 |
|---|---|---|
| 策略模式 | 委托协变/逆变,灵活注入不同策略 | 算法库、支付网关、多态行为 |
| 工厂模式 | 工厂方法返回协变类型 (out) | 服务创建、依赖注入 (DI) |
| 观察者模式 | 事件委托使用逆变参数类型 (in) | UI 事件系统、通知中心 |
| 仓储模式 | 接口协变 (IEnumerable) 提供多态集合 | 数据访问层 (DAL) |
作者:世纪末的魔术师
出处:https://www.cnblogs.com/Firepad-magic/
Unity最受欢迎插件推荐:点击查看
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。

浙公网安备 33010602011771号