图解C#高级教程(四):协变、逆变

news2024/10/3 5:25:24

本章的主题是可变性(variance),这里的可变性更多的是指基类和派生类之间的转换。可变性分为三种:协变(covariance)、逆变(contravariance)和不变(invariance)。

文章目录

  • 1. 协变
    • 1.1 协变的概念
    • 1.2 语法
    • 1.3 使用场景
    • 1.4 代码例子:使用委托实现协变
    • 1.5 代码例子:LINQ 中使用协变
  • 2. 逆变
    • 2.1 逆变的概念
    • 2.2 语法
    • 2.3 使用场景
    • 2.4 代码示例:委托中使用逆变
    • 2.5 代码示例:事件处理中使用逆变
  • 3. 一些问题
    • 3.1 协变和多态的区别
      • 概念区别
      • 主要区别总结

1. 协变

在C#中,协变(Covariance)是一种允许将派生类类型替换为基类类型的特性。这种特性通常用于泛型类型或委托中,特别是在返回类型时。协变的主要目的是提高代码的灵活性和可重用性,使得在使用派生类时能够有效地利用基类的接口或方法。

1.1 协变的概念

协变是指在泛型类型的使用中,允许将某个类型参数替换为该参数的派生类。换句话说,协变允许你在泛型委托或接口中使用更具体的类型。在C#中,协变通常用于返回值的情况。

1.2 语法

在C#中,可以通过使用out关键字来声明协变类型参数。下面是协变的基本语法:

public delegate TResult MyDelegate<out TResult>();

这里,TResult 参数前面加了out关键字,表明这个类型参数是协变的。返回类型可以是派生类。

1.3 使用场景

协变的常见使用场景包括:

  1. 委托:在使用委托时,协变允许将一个返回派生类的委托赋值给返回基类的委托。
  2. LINQ:在LINQ查询中,使用协变来处理不同类型的集合。
  3. 事件处理:在事件处理程序中,使用协变来处理不同类型的事件。

1.4 代码例子:使用委托实现协变

using System;

// 基类
public class Animal
{
    public virtual void Speak()
    {
        Console.WriteLine("Animal speaks");
    }
}

// 派生类
public class Dog : Animal
{
    public override void Speak()
    {
        Console.WriteLine("Dog barks");
    }
}

// 定义一个协变的委托
public delegate T AnimalDelegate<out T>();

class Program
{
    static void Main()
    {
        // 将返回Dog类型的委托赋值给返回Animal类型的委托
        AnimalDelegate<Animal> animalDelegate = GetDog;
        
        // 调用委托并输出结果
        Animal animal = animalDelegate();
        animal.Speak();  // 输出: Dog barks
    }

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

输出:

Dog barks

1.5 代码例子:LINQ 中使用协变

using System;
using System.Collections.Generic;
using System.Linq;

public class Animal
{
    public string Name { get; set; }
}

public class Dog : Animal { }

class Program
{
    static void Main()
    {
        List<Dog> dogs = new List<Dog>
        {
            new Dog { Name = "Buddy" },
            new Dog { Name = "Max" }
        };

        // 使用LINQ进行查询,并返回基类类型的集合
        IEnumerable<Animal> animals = dogs.Select(d => d);

        foreach (var animal in animals)
        {
            Console.WriteLine(animal.Name);  // 输出: Buddy, Max
        }
    }
}

使用协变时,应该注意以下几点:

  1. 只能在返回值中使用协变,而不能在方法参数中使用。
  2. 协变使得代码更加灵活,但也可能引入类型安全问题,因此在使用时应谨慎。

2. 逆变

在C#中,逆变(Contravariance)是与协变相反的特性,允许将基类类型替换为派生类类型。逆变主要用于参数类型的上下文,特别是在方法参数时。通过逆变,我们可以使用更通用的类型来替代特定的类型,从而提高代码的灵活性和可重用性。

2.1 逆变的概念

逆变是指在泛型类型的使用中,允许将某个类型参数替换为该参数的基类。这种特性通常在需要处理不同类型的对象时非常有用。表现在代码上就是某个函数的参数类型是基类类型,但是可以接受其派生类类型的实参。

2.2 语法

在C#中,可以通过使用 in 关键字来声明逆变类型参数。下面是逆变的基本语法:

public delegate void MyDelegate<in T>();

2.3 使用场景

逆变的常见使用场景包括:

  1. 委托:在使用委托时,逆变允许将一个接受派生类的委托赋值给接受基类的委托。
  2. 事件处理:在事件处理程序中,使用逆变来处理不同类型的事件。
  3. 集合操作:在处理集合时,逆变可以帮助简化参数类型的定义。

2.4 代码示例:委托中使用逆变

下面的程序实现了一个基类类型的委托指向参数类型为基类类型的方法,但是在执行委托时,传入给委托的参数类型为派生类类型。

using System;

// 基类
public class Animal
{
    public string Name { get; set; }
}

// 派生类
public class Dog : Animal { }

// 定义一个逆变的委托
public delegate void AnimalAction<in T>(T animal);

class Program
{
    static void Main()
    {
        // 将接受Animal类型的委托赋值给接受Dog类型的委托
        AnimalAction<Animal> animalAction = MakeSound;
        
        Dog dog = new Dog { Name = "Buddy" };
        animalAction(dog);  // 输出: Buddy makes a sound
    }

    static void MakeSound(Animal animal)
    {
        Console.WriteLine($"{animal.Name} makes a sound");
    }
}

2.5 代码示例:事件处理中使用逆变

下面的代码中实现了使用事件和逆变,统一处理不同用户的目的。

    public void AddUser(User user)

输出:

John has been added.
Admin has been added.

使用 in 和 out 关键字只适用于委托和接口,不适用于类、结构和方法。

不包括 in 和 out 关键字的委托和接口类型参数叫做不变。这些类型参数不能用于协变和逆变。

3. 一些问题

3.1 协变和多态的区别

概念区别

  • 多态:多态是指子类可以替代父类的实例,调用相同的方法但可能会有不同的实现。在面向对象编程中,常通过继承和接口来实现多态性。
public class Animal
{
    public virtual void Speak()
    {
        Console.WriteLine("Animal speaks");
    }
}

public class Dog : Animal
{
    public override void Speak()
    {
        Console.WriteLine("Woof!");
    }
}

Animal myDog = new Dog();
myDog.Speak(); // 输出: Woof!
  • 协变:协变是指在泛型类型参数中允许用派生类替代基类。在 C# 中,协变通常与泛型委托和接口相关,允许使用更具体的类型作为返回值。
public delegate T CovariantDelegate<out T>();

public class Animal { }
public class Dog : Animal { }

public static Dog GetDog() => new Dog();

CovariantDelegate<Animal> animalDelegate = GetDog;
Animal animal = animalDelegate(); // 使用协变

主要区别总结

概念范围:

  • 多态是一个更广泛的概念,涵盖了通过接口和继承实现不同类型之间的行为相同。
  • 协变是关于泛型类型参数的特定实现,主要用于返回值的场景。

实现方式:

  • 多态通常通过方法重写(override)和接口实现来实现,允许子类定义父类方法的具体实现。
  • 协变是通过在泛型定义中使用 out 关键字来实现,允许使用更具体的类型作为返回值。

适用场景:

  • 多态主要用于运行时行为的动态选择,允许对象通过父类接口调用不同的实现。
  • 协变主要用于数据结构和类型安全的情况下,特别是在返回类型的灵活性方面。

各位道友,码字不易,记得一键三连呐。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2185624.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Python | Leetcode Python题解之第441题排列硬币

题目&#xff1a; 题解&#xff1a; class Solution:def arrangeCoins(self, n: int) -> int:left, right 1, nwhile left < right:mid (left right 1) // 2if mid * (mid 1) < 2 * n:left midelse:right mid - 1return left

四、Java 基础语法

一、Java 的类、对象、方法和实例变量 一个 Java 程序可以认为是一系列对象的集合&#xff0c;而这些对象通过调用彼此的方法来协同工作。下面简要介绍下类、对象、方法和实例变量的概念。对象&#xff1a;对象是类的一个实例&#xff0c;有状态&#xff08;实例变量&#xff…

MySQL基础练习题49-低质量的问题

目录 题目 准备数据 分析数据 总结 题目 找出 低质量 问题的 ID 集合。如果一个力扣问题的喜欢率&#xff08;喜欢数除以总投票数&#xff09;严格低于 60% &#xff0c;则该问题为低质量问题。 按 problem_id 升序排列返回结果表。 准备数据 Create table If Not Exis…

深度学习基础—卷积神经网络示例

1.卷积神经网络的结构 在之前的博客《深度学习—简单的卷积神经网络》&#xff0c;仅由卷积层构成网络的全部&#xff0c;这还不是标准的网络结构&#xff0c;本文将继续介绍标准的卷积神经网络结构有哪些&#xff1f; 深度学习基础—简单的卷积神经网络https://blog.csdn.net…

STM32-按键控制LED 光敏传感器控制蜂鸣器(江协笔记)

1、按键 2、常见传感器模块 R1 定值电阻 N1 传感器电阻。对于光敏传感器来说&#xff0c;相当于光敏电阻&#xff1b;...... C(滤波电容) 给中间的电压输出进行滤波&#xff0c;用于滤除一些干扰&#xff0c;保证输出电压波形的平滑&#xff08;保持电路稳定&#xff09; …

Pikachu-暴力破解-验证码绕过(on client)

访问页面&#xff0c; 从burpsuite 上看到返回的源代码&#xff1b; 验证码生成时通过 createCode 方法生成&#xff0c;在前端页面生成&#xff1b; 同时也是在前端做的校验&#xff1b; 直接验证&#xff1b;F12 -- 网络&#xff0c;随便输入个账号、密码、验证码&#xff0…

多维度柱状图绘制

图形结果 绘制过程 数据如下 调整柱子宽度 Z轴设置 、 配色表

开源链动2+1模式AI智能名片S2B2C商城小程序源码:流量运营中的价值创造与用户影响

摘要&#xff1a;本文深入探讨在开源链动21模式AI智能名片S2B2C商城小程序源码的背景下&#xff0c;流量的激活、信任建立、圈层沉淀以及裂变等流量运营现象。分析流量运营成本与用户消费意识的关系&#xff0c;强调内容在赋予流量价值以影响用户感知和消费判断方面的重要性。 …

基于yolov8深度学习的120种犬类检测与识别系统python源码+onnx模型+评估指标曲线+精美GUI界面目标检测狗类检测犬类识别系统

【算法介绍】 基于YOLOv8深度学习的120种犬类检测与识别系统是一款功能强大的工具&#xff0c;该系统利用YOLOv8深度学习框架&#xff0c;通过21583张图片的训练&#xff0c;实现了对120种犬类的精准检测与识别。 该系统基于Python与PyQt5开发&#xff0c;具有简洁的UI界面&a…

当AI成为作家,人工智能在写作领域的崛起

AI写作技术的应用正在多个领域展现出其强大的潜力和价值&#xff0c;它不仅极大地提升了内容创作的效率&#xff0c;还为创作者提供了一个全新的创作伙伴。 随着技术的进步&#xff0c;AI写作工具越来越能够理解复杂的语境和用户需求&#xff0c;帮助创作者生成高质量的内容。…

DpCas 镜头场景分割 Scene Segmentation

开源项目 - DpCas 镜头场景分割 Scene Segmentation 开源项目地址&#xff1a;https://gitcode.net/EricLee/dpcas 示例&#xff1a;

写出第一个php程序

一、打开vscode&#xff0c;下载chinese插件、php debug、phpintelephense 二、下载完上方图片插件后&#xff0c;创建一个PHP文件&#xff0c;1.php 三、执行命令&#xff0c;成功输出

pytorch搭建神经网络(手搓方法)

假如我们有一个数据集形状为(348,14)。即有348个记录&#xff0c;每个记录有14个特征值。 我们想要搭建一个如下的神经网络&#xff1a; import torch import numpy as np# 创建数据集: 每个样本有14个特征 x_train np.array([[0.5, -1.2, 0.3, 0.8, 1.0, -0.5, 2.3, 1.2, -0…

在Ubuntu 20.04中安装CARLA

0. 引言 CARLA (Car Learning to Act) 是一款开源自动驾驶模拟器&#xff0c;其支持自动驾驶系统全管线的开发、训练和验证&#xff08;Development, Training, and Validation of autonomous driving systems&#xff09;。Carla提供了丰富的数字资产&#xff0c;例如城市布局…

前端编程艺术(2)----CSS

目录 1.CSS 2.CSS引入 3.选择器 1.标签选择器 2.类选择器 3.id选择器 4.属性选择器 5.后代选择器 5.直接子元素选择器 6.伪类选择器 链接相关 动态伪类 结构化伪类 否定伪类 其他伪类 UI元素状态伪类 4.字体 1.font-family 2.font-size 3.font-style 4.fo…

Linux查找隐藏病毒进程

工具连接 下载工具不要分&#xff0c;随便下 下载后修改工具名&#xff1a;如修改为lsof、ps、top等并为工具加入执行权限 2、 直接执行即可&#xff0c;与正常命令用法一致&#xff08;截图如下&#xff09;

足球预测推荐软件:百万数据阐述百年足球历史-大数据模型量化球员成就值

我开始创建这个模型是从梅西22世界杯夺冠第二天开始准备的&#xff0c;当时互联网上充斥了太多了个人情感的输出&#xff0c;有的人借题对C罗冷嘲热讽&#xff0c;有的人质疑梅西的阿根廷被安排夺冠不配超越马拉多纳做GOAT。作为一个从2002年开始看球的球迷&#xff0c;说实话有…

linux自用小手册

一、GDB常用命令 想用gdb调试C或C程序&#xff0c;编译时需要加-g选项&#xff0c;编译出的文件为debug状态&#xff08;如果不加则是release状态&#xff09;&#xff0c;且不可以加-O选项进行优化。 命令简写解释set args 设置程序传递的参数 例&#xff1a;./demo -v value…

【MySQL报错】---Data truncated for column ‘age‘ at row...

目录 一、前言二、问题分析三、解决办法 一、前言 欢迎大家来到权权的博客~欢迎大家对我的博客进行指导&#xff0c;有什么不对的地方&#xff0c;我会及时改进哦~ 博客主页链接点这里–>&#xff1a;权权的博客主页链接 二、问题分析 问题一修改表结构 XXX 为 not n…

指针 (5)

目录 1. 字符指针变量 2. 数组指针变量 3. ⼆维数组传参的本质 4. 函数指针变量 5.typedef 关键字 6 函数指针数组 7.转移表 计算器的⼀般实现 1. 字符指针变量 在指针的类型中我们知道有⼀种指针类型为字符指针 char* #include <stdio.h> int main() {char* ch …