扫清C#泛型的迷障 - 从基础到实战

ops/2024/12/17 1:30:33/

什么是泛型?

泛型(Generics)是C#的一种特性,它允许你在编写代码时,不指定具体的类型,而是使用类型参数作为占位符。这样一来,你的代码就可以对多种类型进行复用,增加了灵活性,同时还能保持类型安全。

简单来说,泛型就是一种“模板”,你可以用它来创建可重用、类型安全的代码。


为什么需要泛型?

在没有泛型之前,如果你想创建一个可以存储任意类型对象的集合,比如 ArrayList,你可能会这样做:

ArrayList list = new ArrayList();
list.Add(1);
list.Add("Hello");
list.Add(DateTime.Now);

这样虽然灵活,但有两个主要问题:

  • 类型安全问题:从集合中取出元素时,需要进行类型转换,如果转换错误,会导致运行时异常。

  • 性能问题:对于值类型(如int),在添加到集合时,需要进行装箱(Boxing);取出时,需要拆箱(Unboxing),这会影响性能。


泛型的优势

  • 类型安全:在编译时就能检查类型错误,避免运行时异常。
  • 提高性能:避免了装箱和拆箱操作。
  • 代码复用:可以编写更通用的代码,对多种类型适用。

使用泛型的示例

C#提供了很多泛型集合类型,比如List、Dictionary<TKey, TValue>等。

示例:使用List

List<int> numbers = new List<int>();
numbers.Add(1);
numbers.Add(2);
numbers.Add(3);foreach (int number in numbers)
{Console.WriteLine(number);
}

在这里,List表示一个存储整数的列表。由于指定了类型参数为int,所以列表中只能添加整数,编译器会帮你检查类型。


如何自定义泛型

接下来,我们看看如何自定义泛型类和泛型方法。

1. 自定义泛型类
示例:创建一个通用的存储盒子

public class Box<T>
{private T item;public void Put(T item){this.item = item;Console.WriteLine($"已将 {item} 放入盒子。");}public T Get(){Console.WriteLine($"从盒子中取出了 {item}。");return item;}
}

解释:

  • Box:T是类型参数,表示盒子可以存储任意类型的物品。
  • Put方法:用于将物品放入盒子。
  • Get方法:用于从盒子中取出物品。

使用泛型类

// 创建一个存储整数的盒子
Box<int> intBox = new Box<int>();
intBox.Put(123);
int number = intBox.Get();
Console.WriteLine($"取出的数字是:{number}");// 创建一个存储字符串的盒子
Box<string> strBox = new Box<string>();
strBox.Put("Hello, 泛型!");
string message = strBox.Get();
Console.WriteLine($"取出的消息是:{message}");

输出:

已将 123 放入盒子。
从盒子中取出了 123。
取出的数字是:123
已将 Hello, 泛型! 放入盒子。
从盒子中取出了 Hello, 泛型!。
取出的消息是:Hello, 泛型!

2. 自定义泛型方法
有时候,你不需要整个类都是泛型的,只需要某个方法是泛型。

示例:创建一个交换两个变量值的泛型方法

public class Utils
{public static void Swap<T>(ref T a, ref T b){T temp = a;a = b;b = temp;Console.WriteLine($"交换后,a = {a},b = {b}");}
}

解释:

  • Swap:定义了泛型方法,其中T是类型参数。
  • ref T a:按引用传递参数,使交换在方法外也有效。

使用泛型方法:

int x = 10;
int y = 20;
Console.WriteLine($"交换前,x = {x},y = {y}");
Utils.Swap<int>(ref x, ref y);string s1 = "你好";
string s2 = "世界";
Console.WriteLine($"交换前,s1 = {s1},s2 = {s2}");
Utils.Swap<string>(ref s1, ref s2);

输出:

交换前,x = 10,y = 20
交换后,a = 20,b = 10
交换前,s1 = 你好,s2 = 世界
交换后,a = 世界,b = 你好

3. 泛型约束

有时候,我们希望对类型参数进行限制,确保传入的类型具备某些特性,可以使用泛型约束。

示例:限制类型参数必须实现某个接口

public interface IAnimal
{void Speak();
}public class Dog : IAnimal
{public void Speak(){Console.WriteLine("汪汪!");}
}public class Cat : IAnimal
{public void Speak(){Console.WriteLine("喵喵!");}
}public class AnimalShelter<T> where T : IAnimal
{private List<T> animals = new List<T>();public void AddAnimal(T animal){animals.Add(animal);Console.WriteLine("添加了一只动物。");}public void LetAnimalsSpeak(){foreach (var animal in animals){animal.Speak();}}
}

解释:

  • where T : IAnimal:泛型约束,表示T必须是IAnimal接口的实现类。
  • AnimalShelter:动物收容所,可以收容任何实现了IAnimal的动物。

使用泛型约束的类

AnimalShelter<IAnimal> shelter = new AnimalShelter<IAnimal>();
shelter.AddAnimal(new Dog());
shelter.AddAnimal(new Cat());
shelter.LetAnimalsSpeak();

输出:

添加了一只动物。
添加了一只动物。
汪汪!
喵喵!

常用的泛型约束:

  • where T : struct:T必须是值类型。
  • where T : class:T必须是引用类型。
  • where T : new():T必须有一个无参数的公共构造函数。
  • where T : 基类名:T必须继承指定的基类。
  • where T : 接口名:T必须实现指定的接口。
  • where T : U:T必须是类型U或U的子类。

总结

  • 泛型让你能够编写可复用、类型安全的代码。
  • 自定义泛型类:在类名后使用,在类内部可以使用类型参数T来表示任意类型。
  • 自定义泛型方法:在方法名前使用,方法内部可以使用类型参数T。
  • 泛型约束:使用where关键字对类型参数进行限制,保证类型安全和功能的正确性。

动手实践

  • 题目1:使用泛型类

    任务: 创建一个自定义的泛型类Storage<T>,该类用于存储和管理一个对象。实现以下方法:

    • StoreItem(T item): 存储一个项目。
    • RetrieveItem(): 返回存储的项目。

    要求:

    1. 创建Storage<int>类型的对象,并存储整数42
    2. 创建Storage<string>类型的对象,并存储字符串"Hello, Generics"
    3. 打印存储的项目内容。

    预期输出:

    Stored integer: 42 Stored string: Hello, Generics 
    

    题目2:使用泛型方法

    任务: 创建一个类ArrayUtils,并在其中实现一个泛型方法FindMax<T>(T[] array),该方法用于返回数组中最大的元素。

    要求:

    1. 使用FindMax<int>方法查找整数数组[2, 5, 1, 9, 6]中的最大值。
    2. 使用FindMax<double>方法查找浮点数数组[1.5, 8.4, 3.2]中的最大值。
    3. 打印出每个数组的最大值。

    预期输出:

    Max in integer array: 9 Max in double array: 8.4 
    

    题目3:泛型约束的应用

    任务: 创建一个接口IFlyable,其中包含一个方法Fly()。然后,创建一个泛型类FlyingZoo<T>,该类可以存储实现IFlyable接口的对象,并提供方法让这些对象执行飞行动作。

    要求:

    1. 创建类BirdAirplane,并实现接口IFlyable
    2. FlyingZoo中,实现方法Add(T item)LetThemFly()
    3. 将3个Bird对象和1个Airplane对象添加到FlyingZoo中,并执行LetThemFly()方法。

    预期输出:

    Bird is flying! Bird is flying! Bird is flying! Airplane is flying in the sky! 
    

http://www.ppmy.cn/ops/142511.html

相关文章

C语言学习day18:字符串操作/ANSI编码/宽字节/消息框/软件/游戏编码/逆向分析中的编码

今天我们将学习字符串操作&#xff0c;为什么要着重来说这个呢&#xff1f;因为这是为我们之后window开发和api做准备。好的&#xff0c;我们现在正式开始&#xff1a; 字符串 字符串就是一串文字。 比如&#xff1a;"好好学习&#xff0c;天天向上"就是一个字符串…

SpringBoot左脚进门之Maven管理家

一、概念 Maven 是一个项目管理和整合工具。通过对 目录结构和构建生命周期 的标准化&#xff0c; 使开发团队用极少的时间就能够自动完成工程的基础构建配置。 Maven 简化了工程的构建过程&#xff0c;并对其标准化&#xff0c;提高了重用性。 Maven 本地仓库 (Local Reposi…

淘宝详情网页爬虫:技术解析与实战指南

引言 淘宝作为中国最大的电商平台之一&#xff0c;拥有海量的商品数据。对于开发者来说&#xff0c;获取淘宝商品详情接口是一个常见的需求。本文将介绍如何使用Python编写爬虫&#xff0c;获取淘宝商品详情信息&#xff0c;并探讨在实际应用中可能遇到的挑战与解决方案。 环…

Element Plus Table 组件树形渲染实现方法

Element Plus的Table组件通过指定列表数据的children属性&#xff0c;实现树形数据的渲染&#xff1b;同时使用row-key标识唯一的行&#xff0c;依赖排序和子节点数据结构&#xff0c;以实现连动操作。 重要的设置有&#xff1a; 树形渲染配置项&#xff1a; 通过tree-props 配…

nodeJS转换视频格式

系统需要先安装 FFmpeg Download FFmpeg node安装模块 npm install fluent-ffmpeg 使用示例 把 wmv 格式转换 mp4 格式 const ffmpeg require(fluent-ffmpeg) const path require(path)function convertWmvToMp4(inputPath, outputPath) {ffmpeg(inputPath).output(outputP…

React基础学习

React基础 &#x1f4e3; &#x1f4e3; &#x1f4e3; &#x1f4e2;&#x1f4e2;&#x1f4e2; ☀️☀️点开就是缘分认识一下&#xff0c;我是小冷。是一个兴趣驱动自学练习两年半的的Java工程师。 &#x1f4d2; 一位十分喜欢将知识分享出来的Java博主⭐️⭐️⭐️&#x…

Vue3之响应式系统详解

Vue3中的响应式系统是其核心功能之一&#xff0c;它使得数据变化能够自动触发视图更新&#xff0c;从而简化了开发过程&#xff0c;提高了开发效率。本文将详细阐述Vue3中的响应式系统&#xff0c;包括其核心概念、工作原理、实现方式、应用场景以及优势。同时&#xff0c;本文…

web自动化测试框架playwright

一、背景&#xff1a;UI自动化的痛点&#xff1a; 1、设计脚本耗时&#xff1a; 需要思考要如何模拟用户的操作&#xff0c;如何触发页面的事件&#xff0c;还要思考如何设计脚本&#xff0c;定位和操作要交互的元素、路径、位置&#xff0c;再编写代码逻辑&#xff0c;往复循…