[C#]泛型类与泛型方法

一、C++与C#泛型写法区别

复制代码
//C++ 写法(你已经会的)
template<tempname T>
class Stack
{
PUblic:
    void Push(T x)
    {
        Data.push_back(x);
    }
    T top()
    {
        T x = data.back();
        data.pop_back();
        return x;
    }
private:
    vector<T>Data;
}

// 泛型函数

template<tempname T>
void Print(T item)
{
    cout<<item<< endl;
}
Stack<int> si;       // 实例化
Stack<string> ss;

复制代码
//c#写法
class Stack<T>
{
    private List<T> _data = new();          // new() 目标类型 new,省类型名
    public void Push(T x) => _data.Add(x);
    public T Pop()
    {
         var item = _data[^1];               // [^1] = 最后一个,C# 索引运算符
         _data.RemoveAt(_data.Count - 1);
         return item;
    }
     public int Count => _data.Count;
}

// 泛型方法 ------ <T> 写在方法名后
void Print<T>(T item) => Console.WriteLine(item);

Stack<int> si = new();                      // 实例化,类型靠右边推断
Stack<string> ss = new();
1.1对照图

|--------------------------------------|-------------------------|---------------------------------------|
| C++ | C# | 备注 |
| template<typename T> | class Stack<T> | C# 把 <T> 直接写在类名后,没有 template<> 前缀 |
| Stack<int> | Stack<int> | 实例化语法完全一样 |
| template<typename T> void Print(T) | void Print<T>(T item) | C# 的 <T> 写在方法名和参数列表之间 |
| 编译期源码展开 | 运行时具化 | ⚠️ 见第 3 步,这是灵魂差异 |

💡 一个小甜点:C# 的 new() 叫目标类型 new,Stack<int> si = new(); 里右边不用再写 Stack<int>(),编译器看左边猜右边。C++17 的 Stack<int> si{}; 类似。


二、泛型集合

C# 日常 90% 的泛型用在这两个集合上,记住这个映射就行:

|---------------------------|-------------------------|---------------|
| C++ | C# | 说明 |
| std::vector<T> | List<T> | 动态数组,最常用 |
| std::unordered_map<K,V> | Dictionary<K,V> | 哈希表 |
| std::map<K,V> | SortedDictionary<K,V> | 有序(红黑树) |
| v.push_back(x) | list.Add(x) | |
| v.size() | list.Count | 注意是属性不是方法 |
| vi | listi | 索引访问一样 |
| v.back() | list\^1 | C# 用 ^ 从后往前数 |

复制代码
List<int> = new() {1,2,3};        //初始器
nums.Add(4);
Console.WriteLine(nums[^1]);               // 4,最后一个

Dictionary<string, int> ages = new()
{
    ["张三"] = 20;
    ["李四"] = 30;
};
Console.WriteLine(ages["张三"]);  // 20

三、核心差异------为什么 C# 泛型不能用 +?

这是今天最该花时间的地方。看这段对比:

4.1 C++ 模板:编译期源码展开(duck typing)
复制代码
template<typename T>
T Add(T a, T b) { return a + b; }    // 只要 T 有 operator+,就能编过

Add(3, 5);                 // OK,int 有 +
Add(string("a"), string("b"));  // OK,string 有 +
Add(Thing{}, Thing{});     // 只要 Thing 重载了 +,就 OK

C++ 编译器看到 Add<int>,就把模板里每个 T 替换成 int,生成一份专门的 Add 代码。它不检查「T 有没有 +」,它只是把代码抄一遍,等替换完再看 int + int 合不合法。模板的本质是高级的文本替换/代码生成。

4.2 C# 泛型:运行时具化(reified),编译期只认一份 IL
复制代码
T Add<T>(T a, T b) => a + b;   // ❌ 编译错误!运算符 '+' 不能应用于类型 'T'

为什么错?因为 C# 编译器在编译 Add<T> 时,它真的不知道 T 是什么。它只编译出一份泛型 IL 代码,这份代码要在运行时、当你传 int 进去时才「具化」成 Add<int>。编译期它没有任何理由相信 T 一定有 +------万一你传个 Person 进来呢?Person + Person 不存在啊。所以编译器直接拒绝。

这就是为什么 C# 泛型叫 reified(具化):泛型类型 List<int> 在运行时是一个真实存在的、具体的类型,而不是编译后就被擦除掉(Java 那种叫类型擦除 erasure)。但代价是------编译期对 T 能做什么一无所知,除非你用约束告诉它。

4.3 这个差异会带来什么后果?

|------------------|---------------|---------------------------------|
| 场景 | C++ 模板 | C# 泛型 |
| a + b | ✅ 只要 T 有 + 就行 | ❌ 直接报错 |
| new T() | ✅ 默认就行 | ❌ 要加 where T : new() 约束 |
| T t = null | ✅ 指针类型可以 | ❌ 要加 where T : class 约束 |
| 调 t.CompareTo(x) | ✅ 有这个方法就行 | ❌ 要加 where T : IComparable<T> |

四、总结

一句话总结:C++ 模板是「先抄代码再检查」,C# 泛型是「先检查约束再用」。C# 想用 T 的任何能力,都必须用 where 约束提前声明。你只要记住「C# 泛型对 T 一无所知」这个直觉就够了。

复制代码
using System;
using System.Collections.Generic;

// ---------- 1. 泛型类 Stack<T> ----------
class Stack<T>
{
    private readonly List<T> _items = new();

    public void Push(T item) => _items.Add(item);

    public T Pop()
    {
        if (_items.Count == 0)
            throw new InvalidOperationException("栈是空的!");
        var top = _items[^1];
        _items.RemoveAt(_items.Count - 1);
        return top;
    }

    public T Peek()
    {
        if (_items.Count == 0)
            throw new InvalidOperationException("栈是空的!");
        return _items[^1];
    }

    public int Count => _items.Count;
    public bool IsEmpty => Count == 0;
}

// ---------- 2. 泛型方法 ----------
class Printer
{
    // <T> 写在方法名后、参数表前。调用时通常可省略,靠参数推断
    public static void Print<T>(T item) => Console.WriteLine($"值 = {item},类型 = {typeof(T).Name}");
}

// ---------- 3. 主程序 ----------
class Program
{
     static void Main()
    {
        Console.WriteLine("===== 栈:int 类型 =====");
        Stack<int> intStack = new();
        intStack.Push(10);
        intStack.Push(20);
        intStack.Push(30);
        Console.WriteLine($"栈顶(不弹出): {intStack.Peek()}");
        Console.WriteLine($"弹出: {intStack.Pop()}");
        Console.WriteLine($"弹出: {intStack.Pop()}");
        Console.WriteLine($"剩余数量: {intStack.Count}");

        Console.WriteLine("\n===== 栈:string 类型(同一个类,不同 T)=====");
        Stack<string> strStack = new();
        strStack.Push("你好");
        strStack.Push("世界");
        Console.WriteLine($"弹出: {strStack.Pop()}");
        Console.WriteLine($"弹出: {strStack.Pop()}");

        // intStack.Push("字符串");  // ❌ 编译错误!类型安全:int 栈不能塞 string
        // 这就是泛型相比 C++ 模板的最大日常价值:编译期类型检查 + 无装箱

         Console.WriteLine("\n===== 泛型方法 =====");
        Printer.Print(42);              // 推断 T = int
        Printer.Print("泛型方法");      // 推断 T = string
        Printer.Print(3.14);            // 推断 T = double

        Console.WriteLine("\n===== List<T> / Dictionary<K,V> 快速体验 =====");
        List<string> names = new() { "张三", "李四", "王五" };
        names.Add("赵六");
        foreach (var n in names) Console.WriteLine($"  - {n}");

        Dictionary<string, int> score = new()
        {
            ["数学"] = 90,
            ["语文"] = 85
        };
        score["英语"] = 95;
        Console.WriteLine($"数学成绩: {score["数学"]}");
    }
}
相关推荐
benben0441 小时前
大模型之基于PEFT的SFT微调实战篇
开发语言·python
caishenzhibiao1 小时前
顺势交易矩阵主图 同花顺期货通指标
java·c语言·c#
Lhan.zzZ1 小时前
深入理解 windeployqt:混合 C++/Qt 项目的打包指南
开发语言·c++·qt
我命由我123451 小时前
Android 开发 - 广播组件(标准广播、有序广播、静态注册广播、分钟到达广播、网络变更广播...)
android·java·开发语言·网络·java-ee·android studio·android-studio
TDengine (老段)2 小时前
TDengine Node.js 与 C# 连接器 — Web 服务与 .NET 集成
大数据·数据库·node.js·c#·.net·时序数据库·tdengine
米码收割机2 小时前
【Python】Python Django+Vue3校园自习室预约管理系统(源码+文档+PPT)【独一无二】
开发语言·python·django
AstartesEternal2 小时前
python第二次作业(列表,字典)
开发语言·python
zh路西法2 小时前
【Navigation2进阶】(十二):自主探索性能优化与 RIG 算法推导与 Nav2 插件实现
c++·dijkstra·ros2·rrt·navigation2·前沿探索
星恒随风2 小时前
C++ STL 详解:set 与 multiset 的使用、区间查询和算法应用
开发语言·c++·笔记·学习·算法