当前位置: 首页 > news >正文

在C++基础上理解CSharp-6

1:本篇学习目标

  • 理解泛型的核心思想,掌握 C# 泛型的基本语法
  • 彻底搞懂 C# 泛型与 C++ 模板的本质区别,消除认知误区
  • 掌握泛型类、泛型方法的定义与使用
  • 熟练运用 6 种泛型约束,理解每种约束的作用和适用场景
  • 掌握List<T>Dictionary<TKey, TValue>两大核心泛型集合的常用操作
  • 了解Queue<T>Stack<T>HashSet<T>等常用泛型集合的用法
  • 所有知识点均与 C++ 模板、STL 容器进行深度对比

2:泛型概述

泛型(Generics)是一种允许我们在定义类、方法、接口时不指定具体类型,而是在使用时再指定类型的技术。泛型的核心价值是类型安全 + 代码复用

1:为什么需要泛型

在没有泛型的时代,如果我们想实现一个通用的集合,只能使用object类型:

public class ArrayList { private object[] _items; public void Add(object item) { ... } public object this[int index] { get { ... } } }

这种方式有两个严重问题:

  1. 类型不安全:可以向集合中添加任何类型的对象,取出时需要强制类型转换,容易出现运行时异常
  2. 性能损耗:值类型存入时需要装箱,取出时需要拆箱,带来显著的性能开销

泛型完美解决了这两个问题:

public class List<T> { private T[] _items; public void Add(T item) { ... } public T this[int index] { get { ... } } }

使用泛型后,List<int>只能存储int类型,List<string>只能存储string类型,编译时就能保证类型安全,也不需要装箱拆箱。

2:C#泛型与C++模板的本质区别

这是 C++ 开发者最容易误解的知识点,两者虽然语法相似,但底层实现机制完全不同:

对比维度C# 泛型C++ 模板
实例化时机运行时由 CLR 完成编译时由编译器完成
代码生成方式所有引用类型共享同一份代码,值类型各自生成独立代码每个类型都生成独立的代码(模板展开)
类型检查编译时对泛型约束进行检查,运行时保证类型安全编译时对每个具体类型进行检查,错误信息晦涩
约束系统有明确的where约束语法,约束类型的能力C++20 之前只有隐式约束,C++20 引入 Concepts
泛型参数只能是类型可以是类型、整数、指针等
特化不支持模板特化支持完全特化和偏特化

核心结论:

  • C++ 模板本质是代码生成器,编译时为每个类型生成一份独立的代码
  • C# 泛型本质是运行时类型系统,由 CLR 在运行时处理,更轻量、更类型安全

3:泛型类

泛型类是最常用的泛型形式,在类名后使用尖括号<>包裹类型参数。

1:定义一个泛型类

// 定义一个泛型栈类 public class MyStack<T> { private T[] _items; private int _count; public MyStack(int capacity = 10) { _items = new T[capacity]; _count = 0; } public void Push(T item) { if (_count >= _items.Length) { Array.Resize(ref _items, _items.Length * 2); } _items[_count++] = item; } public T Pop() { if (_count == 0) { throw new InvalidOperationException("栈为空"); } T item = _items[--_count]; _items[_count] = default(T); // 清空引用,帮助GC回收 return item; } public T Peek() { if (_count == 0) { throw new InvalidOperationException("栈为空"); } return _items[_count - 1]; } public int Count => _count; }

2:使用泛型类

// 使用int类型的栈 MyStack<int> intStack = new MyStack<int>(); intStack.Push(1); intStack.Push(2); intStack.Push(3); Console.WriteLine(intStack.Pop()); // 输出 3 Console.WriteLine(intStack.Peek()); // 输出 2 // 使用string类型的栈 MyStack<string> stringStack = new MyStack<string>(); stringStack.Push("Hello"); stringStack.Push("World"); Console.WriteLine(stringStack.Pop()); // 输出 World

3:多个类型参数

泛型类可以有多个类型参数,用逗号分隔:

// 定义一个泛型键值对类 public class MyPair<TKey, TValue> { public TKey Key { get; set; } public TValue Value { get; set; } public MyPair(TKey key, TValue value) { Key = key; Value = value; } public override string ToString() { return $"[{Key}, {Value}]"; } } // 使用 MyPair<int, string> pair = new MyPair<int, string>(1, "张三"); Console.WriteLine(pair); // 输出 [1, 张三]

4:泛型方法

方法也可以是泛型的,即使所在的类不是泛型类。

1:定义泛型方法

public static class ArrayUtils { // 泛型方法:交换数组中两个元素的位置 public static void Swap<T>(T[] array, int index1, int index2) { T temp = array[index1]; array[index1] = array[index2]; array[index2] = temp; } // 泛型方法:查找数组中元素的索引 public static int IndexOf<T>(T[] array, T item) { for (int i = 0; i < array.Length; i++) { if (EqualityComparer<T>.Default.Equals(array[i], item)) { return i; } } return -1; } }

2:调用泛型方法

int[] numbers = { 1, 2, 3, 4, 5 }; ArrayUtils.Swap(numbers, 0, 4); // 现在数组变成 {5, 2, 3, 4, 1} int index = ArrayUtils.IndexOf(numbers, 3); Console.WriteLine(index); // 输出 2

类型推断:调用泛型方法时,编译器通常可以根据参数自动推断出类型参数,不需要显式指定

ArrayUtils.Swap<int>(numbers, 0, 4); // 显式指定类型 ArrayUtils.Swap(numbers, 0, 4); // 编译器自动推断类型,推荐写法

5:泛型约束

默认情况下,泛型类型参数T可以是任何类型,这意味着我们只能对T执行object类拥有的操作(如ToString()Equals()等)。

如果我们需要对T执行更多操作,就需要使用泛型约束来限制类型参数的范围。C# 使用where关键字指定约束。

1:六种泛型约束

约束作用对应 C++ 概念
where T : structT 必须是值类型类似std::is_arithmetic_v<T>
where T : classT 必须是引用类型类似std::is_class_v<T>
where T : new()T 必须有公共的无参数构造函数类似std::is_default_constructible_v<T>
where T : 基类名T 必须继承自指定的基类类似std::is_base_of_v<Base, T>
where T : 接口名T 必须实现指定的接口类似 Concepts 中的接口约束
where T : UT 必须继承自另一个类型参数 U偏特化的一种形式

2:约束使用示例

值类型约束

public class NumberCalculator<T> where T : struct { public T Add(T a, T b) { // 注意:即使有struct约束,也不能直接使用+运算符 // 需要借助泛型数学或动态方法,C# 11引入了INumber<T>接口解决此问题 return (T)((dynamic)a + (dynamic)b); } }

引用类型约束

public class ReferenceTypeContainer<T> where T : class { private T _item; public void SetItem(T item) { _item = item; } public bool IsNull() { return _item == null; // 有class约束才能用==null比较 } }

构造函数约束

public class Factory<T> where T : new() { public T CreateInstance() { return new T(); // 有new()约束才能new T() } }

基类约束

public class AnimalContainer<T> where T : Animal { private List<T> _animals = new List<T>(); public void Add(T animal) { _animals.Add(animal); } public void MakeAllSound() { foreach (T animal in _animals) { animal.MakeSound(); // 有基类约束才能调用基类方法 } } }

接口约束

public class ComparableUtils<T> where T : IComparable<T> { public static T Max(T a, T b) { return a.CompareTo(b) > 0 ? a : b; // 有接口约束才能调用CompareTo } public static T Min(T a, T b) { return a.CompareTo(b) < 0 ? a : b; } }

3:组合约束

可以将多个约束组合在一起使用:

public class MyContainer<T> where T : class, IDisposable, new() { // T必须是引用类型,实现IDisposable接口,且有公共无参构造函数 }

注意:多个约束有顺序要求:

  1. 主约束(class/struct/ 基类)必须放在最前面
  2. 接口约束放在中间
  3. new()构造函数约束必须放在最后

6:总结

本来想将常用的泛型集合的,但是想了想还是单开一个专题,最后讲吧。

在这篇博客中,我们系统学习了 C# 的泛型系统和常用泛型集合。对于 C++ 开发者来说,核心是理解:

  1. C# 泛型是运行时类型系统,不是编译时代码展开
  2. 泛型约束是保证类型安全的关键,比 C++ 早期的隐式约束更清晰
  3. 泛型集合替代了早期的非泛型集合,既类型安全又高性能
http://www.jsqmd.com/news/1106141/

相关文章:

  • AI 编译优化入门:算子融合不是为了少写几行代码
  • utpasswd命令详解:10个实用参数让密码管理更高效
  • SolidWorks_装配体设计5_自上而下设计
  • AI Agent 编排实战:别让多个智能体互相抢麦
  • 特种行业加固计算机配套的固态硬盘,兼容性问题通常出在哪里?
  • Kiran Biometrics:开源生物识别认证系统的完整指南
  • Java反射基础
  • Frida内存操作避坑指南:从原理到实战的逆向分析核心技能
  • CNN-LSTM-AdaBoost时间序列预测实战指南
  • 大模型推理加速年度趋势:从量化到稀疏化的技术跃迁路径
  • ActiveReports for .NET 20.0J SP1-AIレポートウィザードがさらに進化
  • 大模型推理加速核心:KV Cache 复用机制与内存布局优化
  • 开启 OpenFeign 调用日志打印
  • Nuke Survival Toolkit:150个Nuke插件的终极指南与完整解决方案
  • CAD二次开发中的公差控制
  • Electron + Rust:吉他谱播放器性能优化实战
  • 抖音音频下载终极指南:5分钟掌握免费开源工具
  • 无限集(深圳)8年汽车电子深耕,12+整车厂定点
  • c++复习自存
  • 记录一个标记所有new出来的内存的地址加上TAG
  • AI 辅助:Product Hunt 发布复盘:上线当天之前,准备已经开始
  • Cursor Free VIP破解工具:3分钟解除AI编程助手试用限制的终极指南
  • 西安共享茶室平台开发?时段预约锁房技术源码讲解
  • 封装统一多模态客户端(整合文字对话 + 文生图 + 语音转写)
  • 利用金字塔原理学习PHP的具象化的庖丁解牛
  • 汽车电子散热管理:DRV8213驱动器与MF25060V2风扇实战
  • 【小白也能轻松玩转龙虾】虾壳云一键部署入门攻略,分步搭建桌面端 OpenClaw v2.7.9(附最新安装包)
  • React 渲染性能:组件边界、状态下沉与重渲染治理
  • 后端开发者转型AI大模型的必备技能与实战指南
  • AI 辅助:独立开发者技术选型:最好的技术是能让产品活下去