一句话理解泛型
泛型(Generic)就是给类型留一个"占位符",等到真正使用时再填入具体类型。写一次代码,适用所有类型。
LabVIEW 中类似的概念
Variant — 可以装任何类型,但运行时才知道是什么,取数据要手动转换
Malleable VI (.vim) — 编译时根据输入类型适配,但只限 VI 级别
Malleable VI (.vim) — 编译时根据输入类型适配,但只限 VI 级别
C# 泛型
编译时类型安全 — 写 DataStore<double>,编译器保证里面只能装 double
零运行时开销 — 无装箱、无类型转换、无 Variant 包装
零运行时开销 — 无装箱、无类型转换、无 Variant 包装
用一句话说清楚泛型和 Variant 的区别:Variant 是什么都能装的黑盒子,泛型是编译时就锁定了里面装什么的透明盒子。
泛型类:DataStore<T>
这里的 T 是一个"类型占位符"——你在定义 DataStore 时不知道将来会存什么类型,所以先用 T 代替。
泛型类
// = 类型占位符,T 可以是任何名字(惯例用 T)
public class DataStore<T>
{
private List<T> _items = new(); // List 也泛型化:里面装什么类型取决于 T
// C#9.0+简洁写法,= new List<T>()
//名称前下划线是C#命名习惯,表示私有字段
public void Add(T item)
=> _items.Add(item); // T 类型进来
public T Get(int index)
=> _items[index]; // T 类型出去
}使用时把 T替换成具体类型——同一个类,不同用途:
指定类型
// 存 double 的仓库
var numStore = new DataStore<double>();
numStore.Add(3.14);
double d = numStore.Get(0); // 取出来就是 double,不需要转换
// 存 string 的仓库
var strStore = new DataStore<string>();
strStore.Add("Hello");
string s = strStore.Get(0); // 取出来就是 string
// 存自定义对象的仓库
var sensorStore = new DataStore<Sensor>();
sensorStore.Add(new Sensor { Name = "T1" });
Sensor sensor = sensorStore.Get(0);“关键直觉:编译器看到 DataStore<double> 时,相当于自动生成了一个"专存 double 版的 DataStore";看到 DataStore<string> 时生成"专存 string 版的"。两份代码在底层是独立的,性能等同于直接写 List<double>。”
泛型方法:Swap<T>
泛型也可以只用在方法上,而不需要整个类都泛型化:
泛型方法
// 泛型方法:交换任意类型的两个值
static void Swap<T>(ref T a, ref T b)
//ref表示参数"按引用传递"。方法内部对 a 和 b 的修改,会反映到外部的原变量上,和 LabVIEW 的 DVR 一样
{
T temp = a;
a = b;
b = temp;
}
// 使用:同一个方法,适配所有类型
int x = 1, y = 2;
Swap<int>(ref x, ref y); // x=2, y=1
string s1 = "Hello", s2 = "World";
Swap<string>(ref s1, ref s2); // s1="World", s2="Hello"
// 甚至能自动推断类型——省略 编译器也能猜到
Swap(ref x, ref y); // 等价于 Swap<int>在 LabVIEW 中,如果想让一个 SubVI 同时处理 DBL 和 I32 的交换,通常需要用 Variant 做输入输出,内部再做类型判断。C# 中一行泛型方法就解决了,而且是编译时安全的。
泛型约束:where T : ...
有时你不希望 T 是任意类型——你想限定它必须实现某个接口、必须是引用类型、必须有默认构造函数。
多种泛型约束
// T 必须实现 ICalibratable 接口(对标 LabVIEW Interface)
public class CalibrationRunner<T> where T : ICalibratable
{
public void Run(T device)
{
device.Calibrate(); // 因为 T : ICalibratable,所以能调用 Calibrate()
}
}
// T 必须是引用类型(class 约束)
public class Repository<T> where T : class { ... }
// T 必须有默认构造函数(new() 约束)
public class Factory<T> where T : new()
{
public T Create() => new T();
}
// 多个约束可以叠加
public class Processor<T> where T : Sensor, ICalibratable, new()
{ ... }LabVIEW 对比:Variant 方式

Variant 是 LabVIEW 中处理"未知类型"的传统方式。下面把 Variant 和泛型放在一起对比:
| 特性 | LabVIEW Variant | C# 泛型 <T> |
|---|---|---|
| 类型检查时机 | 运行时(Variant To Data 失败 = 报错) | 编译时(写错类型 = 编译不过) |
| 取数据 | 手动 Variant To Data + 选目标类型 | 直接取出,无需转换 |
| 性能 | 装箱 / 拆箱有 CPU 开销 | 零装箱,原生性能 |
| 代码简洁度 | 存和取都需要额外的转换 VI | 和普通类写法完全一致 |
| 适用场景 | 不同数据类型混用、动态类型场景 | 明确类型、追求性能和安全 |
“类比:Variant 就像贴着"易碎品,到手自己拆"的包裹——你拆开之前不知道里面是什么。泛型就像贴着"内存:double × 10"的包裹——包装和拆箱都在编译期自动处理,运行时无需关心。”