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

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

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

 

 

🌟通俗易懂讲协变与逆变

很多人第一次遇到 协变 (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)
posted @ 2025-02-17 15:51  世纪末の魔术师  阅读(56)  评论(0)    收藏  举报