泛型是避免装箱拆箱最直接、最可靠的方式,其他手段都是妥协或补救;list add()参数为int,全程栈操作零装箱,而arraylist.add()接收object,int传入必装箱分配堆内存并拷贝值。

泛型是避免装箱拆箱最直接、最可靠的方式,其他手段都是妥协或补救。
为什么 List<object></object> 一加整数就装箱
因为 List<object>.Add()</object> 接收的是 object 参数,传入 int 时编译器必须把它包装成堆上对象——哪怕你只插一个数,也分配一次堆内存、拷贝一次值。这不是语法糖,是 CLR 强制行为。
-
ArrayList同理,且没有类型安全,编译期不报错,运行时才可能出InvalidCastException -
List<int></int>的Add()是编译期生成的专用方法,参数是int,全程栈上操作,零装箱 - 连
Dictionary<string int></string>都比Hashtable更快:后者 key/value 全是object,每次取值都要拆箱
IComparable.CompareTo 调用时悄悄二次装箱
值类型实现接口后,用接口变量调用方法,会触发额外装箱。比如:
int x = 42; IComparable comp = x; // 第一次装箱:x → object 包装成 IComparable comp.CompareTo(100); // 第二次装箱:CompareTo 参数是 object,100 又被装箱
这在循环里尤其危险。真正零成本的做法是直接用泛型约束:
- 改写为
int.CompareTo(int):直接调用值类型原生方法,无接口、无装箱 - 泛型方法加
where T : IComparable<t></t>,让 JIT 生成专用代码,绕过接口虚调用 - 避免把值类型先赋给接口变量再调用——那等于主动送它上堆两次
Console.WriteLine 和字符串插值的差异
Console.WriteLine(42) 看似简单,但底层匹配的是 WriteLine(object) 重载,42 必然装箱。而 $"Value: {42}" 在 .NET Core 3+ 已优化:对基础类型(int、bool、DateTime 等)直接走格式化逻辑,不经过 object 中转。
-
string.Format("{0}", 42)仍会装箱:它的参数是object[],每个值类型元素都得装 -
Console.WriteLine($"{value}")安全;但Console.WriteLine(value.ToString())更明确,且不依赖运行时优化 - 日志方法若定义为
void Log(object msg),传DateTime.Now就装箱;改成void Log<t>(T msg) where T : struct</t>或直接接收string,就能避开
拆箱失败不是编译错误,而是运行时陷阱
拆箱要求类型严格一致,不是“能转就行”。下面三行看着像,只有最后一行能过:
object o = 42; // int 装箱 int i = (int)o; // ✅ 正确 long l = (long)o; // ❌ InvalidCastException float f = (float)o; // ❌ 同样抛异常,哪怕数值上可表示
更隐蔽的是 null 拆箱:
-
object o = null; int i = (int)o;抛NullReferenceException,不是InvalidCastException -
int? nullable = null; object o = nullable; int i = (int)o;同样崩,因为可空类型装箱后是null,不是Nullable<int></int>实例 - 只要涉及拆箱,就必须假设上游可能传错类型——单元测试里得覆盖
object来源的各类组合
真正难防的不是“会不会装箱”,而是“你以为没装,其实悄悄装了”。比如把 struct 当 interface 用、在旧版集合里塞数字、甚至只是多写了一个 (object) 强转。这些地方不报错、不告警,只在压测时 GC 时间突然飙升。











