C# 泛型中协变和逆变

协变和逆变都是术语,前者指能够使用比原始指定的派生类型的派生程度更大(更具体的)的类型,后者指能够使用比原始指定的派生类型的派生程度更小(不太具体的)的类型。 泛型类型参数支持协变和逆变,可在分配和使用泛型类型方面提供更大的灵活性。

协变 convariance

样例01

//基类
class Animal{
    public int Legs=4;
}

//派生类
class Dog:Animal{

}

delegate T Factory<T>();

class Program{
    static Dog MakeDog{
        return new Dog();
    }

    static void Main(){
        Factory<Dog> dog=MakeDog;
        Factoru<Animal> animal=dog;//这里编译会报错

        Console.WriteLine(animal().Legs.ToString());
    }
}

上述代码编译器会报错,Main 方法第二行,提示不能隐式把右边类型转换为左边类型。

Dog 是 Animal 的派生类,但委托 Factory<Dog> 没有从 Factory<Animal> 派生。相反,两个委托对象是同级的,它们都是 delegate 类型派生。

再仔细分析一下,我们可以看到如果类型参数只用做输出值,则同样的情况也适用于任何泛型委托。对于所有这样的情况,我们应该可以使用派生类创建委托类型,这样应该能够正常工作,因为调用代码总是期望得到一个基类的引用,这也正是它会得到的。

如果派生类只是用于输出值,那么这种结构化的委托有效性之间的常数关系叫协变。 为了让编译器知道这是我们的期望,必须使用 out 关键字标记委托生命中的类型参数。

为该实例中的委托增加 out 关键字,代码就可以通过编译。

delegate T Factory<out T>();

逆变 contravariance

样例02

//基类
class Animal{
    public int Legs=4;
}

//派生类
class Dog:Animal{

}

delegate void Factory<in T>(T a);

class Program{
    static void ShowLegs(Animal a){
        Console.WriteLine(a.Legs);
    }

    static void Main(){
        Factory<Animal> animal=ShowLegs;
        Factoru<Dog> dog=animal;

        dog(new Dog());
    }
}

和之前的情况相似,默认情况下不可以赋值两种不兼容的类型。但是和之前情况也相似的是,有一些情况可以让这些赋值生效。

这种在期望传入基类时允许传入派生对象的特性叫做逆变。 可以在类型参数中显式的使用 in 关键字来表示。

delegate void Factory<int T>(T a);

接口中的协变和逆变

现在你应该已经理解了协变和逆变可以应用到委托上。其实相同的原则也可以应用到接口上,可以在声明接口的时候使用 outin 关键字。

样例03

class Animal { public string Name; }
class Dog : Animal {};

interface IMyIfc<out T>{
    T GetFirst();
}

class SimpleReturn<T>:IMyIfc<T>{
    public T[] items=new T[2];
    public T GetFirst(){ return items[0]; }
}

class Program{
    static void DoSomething(IMyIfc<Animal> returner){
        Console.WriteLine(returner.GetFirst().Name);
    }

    static void Main(){

        SimpleReturn<Dog> dog=new SimpleReturn<Dog>();
        dog.items[0]=new Dog(){ Name="hashiqi" };
        IMyIfc<Animal> animal=dog;
        DoSomething(animal)
    }
}
C#中,(Covariance)逆变(Contravariance)是编程中的重要特性,它们允许在某些情况下进行更灵活的类型转换,从而提升代码的灵活性重用性。以下是关于这两个概念的详细解释、示例以及使用场景。 ### (Covariance) 允许将一个派生类型给其基类型,这种转换在某些接口或委托中是被支持的。具体来说,通过`out`关键字实现,确保类型参数仅用于输出位置(例如返回)。这使得接口可以接受更具体的类型作为返回。 **示例:** ```csharp interface ICovariant<out T> { T GetItem(); } class Animal { } class Dog : Animal { } class AnimalProvider : ICovariant<Animal> { public Animal GetItem() => new Animal(); } class Program { static void Main() { ICovariant<Dog> dogProvider = new AnimalProvider(); // 允许将AnimalProvider赋给ICovariant<Dog> } } ``` 在这个例子中,`ICovariant<out T>`接口定义了一个返回类型为`T`的方法`GetItem`。由于`Dog`继承自`Animal`,因此`ICovariant<Dog>`可以安全地引用`AnimalProvider`实例[^1]。 **使用场景:** - **LINQ查询**:在LINQ中,常用于`IEnumerable<T>`接口,使得可以从一个`IEnumerable<Derived>`直接赋给`IEnumerable<Base>`。 - **事件处理**:允许事件处理程序处理派生类的事件,增强了事件订阅的灵活性[^3]。 ### 逆变(Contravariance) 逆变则与相反,它允许将一个基类型给其派生类型,通常通过`in`关键字实现。逆变确保类型参数仅用于输入位置(例如方法参数),从而允许传递更通用的类型。 **示例:** ```csharp interface IContravariant<in T> { void Process(T item); } class Animal { } class Dog : Animal { } class AnimalProcessor : IContravariant<Animal> { public void Process(Animal item) { Console.WriteLine("Processing an animal."); } } class Program { static void Main() { IContravariant<Dog> dogProcessor = new AnimalProcessor(); // 逆变允许将AnimalProcessor赋给IContravariant<Dog> dogProcessor.Process(new Dog()); } } ``` 在这个例子中,`IContravariant<in T>`接口定义了一个接受类型为`T`的参数的方法`Process`。由于`Dog`继承自`Animal`,因此`IContravariant<Dog>`可以安全地引用`AnimalProcessor`实例[^4]。 **使用场景:** - **委托事件处理**:逆变常用于委托事件处理,特别是在需要处理不同类型的事件时,允许使用更通用的处理逻辑。 - **比较器排序**:在实现`IComparer<T>`或`IComparable<T>`接口时,逆变可以帮助创建更加通用的比较逻辑[^5]。 ### 总结 逆变C#中提供了强大的类型转换能力,尤其在处理接口委托时。通过`out`关键字实现,适用于只读的输出场景;而逆变通过`in`关键字实现,适用于只写的输入场景。这些特性不仅提高了代码的灵活性,还简化了类型之间的交互[^2]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值