C#中的协变与逆变方式

 更新时间:2024年12月16日 14:14:56   作者:呆呆小雅  
协变和逆变是C#中处理泛型类型参数可变性的两个重要概念,协变允许将派生类型的泛型参数转换为基类型的泛型参数,而逆变允许将基类型的泛型参数转换为派生类型的泛型参数,通过协变和逆变,可以提高代码的灵活性和可重用性,但也需要注意类型参数的限制和安全性

前言

在 C# 中,协变(Covariance)和逆变(Contravariance)是两个重要的概念,主要用于处理泛型类型参数的可变性。

这两个概念允许在泛型类型之间进行更灵活的转换,提高了代码的可重用性和灵活性。

协变

(一)定义与概念

协变允许将一个派生类型的泛型参数转换为基类型的泛型参数。

简单来说,如果有两个泛型接口IFoo和IBar,并且IFoo的泛型参数T是协变的,那么如果A是B的派生类,就可以将IFoo转换为IFoo

(二)使用场景与示例

示例一:泛型接口中的协变

interface IFoo<out T>
{
    T GetValue();
}

假设我们有以下接口定义:

这里的out关键字表示T是协变的。现在我们可以这样使用这个接口:

class Animal {}
class Dog : Animal {}

class Program
{
    static void Main()
    {
        IFoo<Dog> dogFoo = new DogFoo();
        IFoo<Animal> animalFoo = dogFoo; // 协变转换允许

        Animal animal = animalFoo.GetValue();
    }
}

class DogFoo : IFoo<Dog>
{
    public Dog GetValue()
    {
        return new Dog();
    }
}

在这个例子中,由于IFoo的T是协变的,我们可以将IFoo转换为IFoo。

这是因为Dog是Animal的派生类,并且GetValue方法只返回T类型的值,不会对其进行修改,所以这种转换是安全的。

示例二:委托中的协变

delegate T Func<out T>();

class Program
{
    static void Main()
    {
        Func<Dog> dogFunc = GetDog;
        Func<Animal> animalFunc = dogFunc; // 协变转换允许

        Animal animal = animalFunc();
    }

    static Dog GetDog()
    {
        return new Dog();
    }
}

C# 中的委托也可以支持协变。

例如:

这里的委托Func<out T>T是协变的,所以可以将Func<Dog>转换为Func<Animal>

逆变

(一)定义与概念

逆变允许将一个基类型的泛型参数转换为派生类型的泛型参数。

如果有两个泛型接口IFoo<T>IBar<T>,并且IFoo<T>的泛型参数T是逆变的,那么如果AB的派生类,就可以将IFoo<B>转换为IFoo<A>

(二)使用场景与示例

示例一:泛型接口中的逆变

考虑以下接口定义:

interface IBar<in T>
{
    void SetValue(T value);
}

这里的in关键字表示T是逆变的。

现在我们可以这样使用这个接口:

class Animal {}
class Dog : Animal {}

class Program
{
    static void Main()
    {
        IBar<Animal> animalBar = new AnimalBar();
        IBar<Dog> dogBar = animalBar; // 逆变转换允许

        dogBar.SetValue(new Dog());
    }
}

class AnimalBar : IBar<Animal>
{
    public void SetValue(Animal value) {}
}

在这个例子中,由于IBar的T是逆变的,我们可以将IBar转换为IBar。

这是因为SetValue方法接受一个T类型的参数,并且Dog是Animal的派生类,所以可以将Dog类型的参数传递给接受Animal类型参数的方法,这种转换是安全的。

示例二:委托中的逆变

delegate void Action<in T>(T value);

class Program
{
    static void Main()
    {
        Action<Animal> animalAction = SetAnimal;
        Action<Dog> dogAction = animalAction; // 逆变转换允许

        dogAction(new Dog());
    }

    static void SetAnimal(Animal value) {}
}

委托也可以支持逆变。

例如:

这里的委托Action<in T>T是逆变的,所以可以将Action<Animal>转换为Action<Dog>

协变与逆变的限制与注意事项

(一)泛型类型参数的限制

对于协变的泛型类型参数,只能在接口或委托中作为返回值类型出现,不能作为方法的参数类型。

例如,以下代码是不合法的:

interface IFoo<out T>
{
    void DoSomething(T value); // 错误,协变类型不能作为参数
}

对于逆变的泛型类型参数,只能在接口或委托中作为方法的参数类型出现,不能作为返回值类型。

例如,以下代码是不合法的:

interface IBar<in T>
{
    T GetValue(); // 错误,逆变类型不能作为返回值
}

(二)数组的协变与逆变

C#中的数组在一定程度上支持协变,但这种协变是不安全的。

例如:

Animal[] animals = new Dog[10]; // 编译通过,但运行时可能会抛出异常
animals[0] = new Cat(); // 如果这里是一个 Cat 对象,运行时会抛出异常

与数组不同,泛型类型的协变和逆变是安全的,因为编译器会在编译时进行类型检查,确保转换的安全性。

(三)可变性的范围

协变和逆变只适用于引用类型,对于值类型是不适用的。

例如,以下代码是不合法的:

interface IFoo<out T> where T : struct // 错误,协变不能用于值类型
{
    T GetValue();
}

总结

协变和逆变是 C#中强大的概念,可以提高代码的灵活性和可重用性。

通过正确使用协变和逆变,可以在泛型类型之间进行安全的转换,使得代码更加简洁和易于维护。

然而,在使用协变和逆变时,需要注意类型参数的限制和安全性,以避免潜在的运行时错误

以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。

相关文章

  • 从原理到高级应用解析WPF依赖属性

    从原理到高级应用解析WPF依赖属性

    依赖属性(Dependency Property)是WPF中一个核心概念,它扩展了传统的.NET属性系统,本文将从原理到高级应用为大家进行详细介绍,希望对大家有所帮助
    2025-07-07
  • C#从文件或标准输入设备读取指定行的方法

    C#从文件或标准输入设备读取指定行的方法

    这篇文章主要介绍了C#从文件或标准输入设备读取指定行的方法,涉及C#文件及IO操作的相关技巧,具有一定参考借鉴价值,需要的朋友可以参考下
    2015-04-04
  • C#中System.Threading.Tasks库的使用

    C#中System.Threading.Tasks库的使用

    System.Threading.Tasks是.NET的TAP核心,提供异步编程模型,本文主要介绍了C#中System.Threading.Tasks库的使用,具有一定的参考价值,感兴趣的可以了解一下
    2025-06-06
  • WinForm中comboBox控件数据绑定实现方法

    WinForm中comboBox控件数据绑定实现方法

    这篇文章主要介绍了WinForm中comboBox控件数据绑定实现方法,结合实例形式分析了WinForm实现comboBox控件数据绑定的常用方法与相关操作技巧,需要的朋友可以参考下
    2017-05-05
  • c#和sql如何获得时间间隔的方法

    c#和sql如何获得时间间隔的方法

    这篇文章主要介绍了c#和sql如何获得时间间隔的方法,有需要的朋友可以参考一下
    2013-12-12
  • .NET Core使用C#扫描并读取图片中的文字

    .NET Core使用C#扫描并读取图片中的文字

    本文详细讲解了.NET Core使用C#扫描并读取图片中的文字,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2021-12-12
  • c# 获取网页中指定的字符串信息的实例代码

    c# 获取网页中指定的字符串信息的实例代码

    c# 获取网页中指定的字符串信息的实例代码,需要的朋友可以参考一下
    2013-04-04
  • C# 线程安全详解

    C# 线程安全详解

    这篇文章主要介绍了c# 线程安全的用法原理及使用示例,帮助大家更好的理解和使用c#,感兴趣的朋友可以了解下,希望能够给你带来帮助
    2021-09-09
  • WPF利用CommunityToolkit.Mvvm实现级联选择器

    WPF利用CommunityToolkit.Mvvm实现级联选择器

    这篇文章主要介绍了WPF如何利用CommunityToolkit.Mvvm实现级联选择器,文中的示例代码讲解详细,对我们的学习或工作有一定帮助,需要的小伙伴可以参考一下
    2023-12-12
  • 基于WPF简单实现Meesage消息提醒

    基于WPF简单实现Meesage消息提醒

    这篇文章主要介绍了如何利用WPF简单实现Meesage消息提醒,文中的示例代码讲解详细,对我们学习或工作有一定帮助,需要的可以参考一下
    2023-07-07

最新评论