![[C#]泛型类与泛型方法](http://pic.xiahunao.cn/yaotu/[C#]泛型类与泛型方法)
一、C与C#泛型写法区别//C 写法你已经会的 templatetempname T class Stack { PUblic: void Push(T x) { Data.push_back(x); } T top() { T x data.back(); data.pop_back(); return x; } private: vectorTData; } // 泛型函数 templatetempname T void Print(T item) { coutitem endl; } Stackint si; // 实例化 Stackstring ss;//c#写法 class StackT { private ListT _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 PrintT(T item) Console.WriteLine(item); Stackint si new(); // 实例化类型靠右边推断 Stackstring ss new();1.1对照图CC#备注templatetypename Tclass StackTC# 把 T 直接写在类名后没有 template 前缀StackintStackint实例化语法完全一样templatetypename T void Print(T)void PrintT(T item)C# 的 T 写在方法名和参数列表之间编译期源码展开运行时具化⚠️ 见第 3 步这是灵魂差异 一个小甜点C# 的 new() 叫目标类型 newStackint si new(); 里右边不用再写 Stackint()编译器看左边猜右边。C17 的 Stackint si{}; 类似。二、泛型集合C# 日常 90% 的泛型用在这两个集合上记住这个映射就行CC#说明std::vectorTListT动态数组最常用std::unordered_mapK,VDictionaryK,V哈希表std::mapK,VSortedDictionaryK,V有序红黑树v.push_back(x)list.Add(x)v.size()list.Count注意是属性不是方法v[i]list[i]索引访问一样v.back()list[^1]C# 用 ^ 从后往前数Listint new() {1,2,3}; //初始器 nums.Add(4); Console.WriteLine(nums[^1]); // 4最后一个 Dictionarystring, int ages new() { [张三] 20; [李四] 30; }; Console.WriteLine(ages[张三]); // 20三、核心差异——为什么 C# 泛型不能用 这是今天最该花时间的地方。看这段对比4.1 C 模板编译期源码展开duck typingtemplatetypename T T Add(T a, T b) { return a b; } // 只要 T 有 operator就能编过 Add(3, 5); // OKint 有 Add(string(a), string(b)); // OKstring 有 Add(Thing{}, Thing{}); // 只要 Thing 重载了 就 OKC 编译器看到 Addint就把模板里每个 T 替换成 int生成一份专门的 Add 代码。它不检查「T 有没有 」它只是把代码抄一遍等替换完再看 int int 合不合法。模板的本质是高级的文本替换/代码生成。4.2 C# 泛型运行时具化reified编译期只认一份 ILT AddT(T a, T b) a b; // ❌ 编译错误运算符 不能应用于类型 T为什么错因为 C# 编译器在编译 AddT 时它真的不知道 T 是什么。它只编译出一份泛型 IL 代码这份代码要在运行时、当你传 int 进去时才「具化」成 Addint。编译期它没有任何理由相信 T 一定有 ——万一你传个 Person 进来呢Person Person 不存在啊。所以编译器直接拒绝。这就是为什么 C# 泛型叫 reified具化泛型类型 Listint 在运行时是一个真实存在的、具体的类型而不是编译后就被擦除掉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 : IComparableT四、总结一句话总结C 模板是「先抄代码再检查」C# 泛型是「先检查约束再用」。C# 想用 T 的任何能力都必须用 where 约束提前声明。你只要记住「C# 泛型对 T 一无所知」这个直觉就够了。using System; using System.Collections.Generic; // ---------- 1. 泛型类 StackT ---------- class StackT { private readonly ListT _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 PrintT(T item) Console.WriteLine($值 {item}类型 {typeof(T).Name}); } // ---------- 3. 主程序 ---------- class Program { static void Main() { Console.WriteLine( 栈int 类型 ); Stackint 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); Stackstring 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 ListT / DictionaryK,V 快速体验 ); Liststring names new() { 张三, 李四, 王五 }; names.Add(赵六); foreach (var n in names) Console.WriteLine($ - {n}); Dictionarystring, int score new() { [数学] 90, [语文] 85 }; score[英语] 95; Console.WriteLine($数学成绩: {score[数学]}); } }