C#主构造函数中仅用于初始化属性的传入变量是否会被保留?
我完全懂你的顾虑——尤其是在每秒处理上千个对象的场景下,哪怕一点点不必要的内存开销累积起来都可能出问题!先给你吃个定心丸:你用主构造函数传入的DeviceData对象,不会被DeviceDataStatus类持有,编译器能精准识别出你只用到了它的属性值,不会把这个引用留在类实例里,GC完全可以正常回收原DeviceData对象。
咱们来拆解一下你观察到的细节:
两种构造写法的实际效果完全等价
你写的主构造函数版本:
public class DeviceDataStatus(DeviceData data) { public uint DeviceId { get; } = data.DeviceId; public DeviceStatus Status { get; } = data.Status; }
和传统构造函数版本:
public class DeviceDataStatus { public uint DeviceId { get; } public DeviceStatus Status { get; } public DeviceDataStatus(DeviceData data) { DeviceId = data.DeviceId; Status = data.Status; } }
在编译后的IL层面几乎没有区别。编译器会把主构造函数里的属性初始化逻辑,直接转换成和传统构造函数一样的“构造函数内赋值”操作,不会额外保留data参数的引用。
关于Sharplab反编译结果的“奇怪”现象
你看到的反编译代码里,主构造函数版本的构造方法是空的,字段直接用data.DeviceId初始化——这其实是反编译工具的语法糖还原方式,不是实际运行的逻辑。真实的编译结果中,这些字段赋值是在构造函数内部执行的,而且不会将data参数存储为类的成员变量。你可以对比两者的IL代码(而不是反编译后的C#),会发现它们几乎完全一致。
结构体场景的额外验证
你提到如果传入的是大结构体会不会有双倍内存开销——放心,同样不会。主构造函数只会提取你需要的字段值赋值给新类的属性,不会复制整个结构体并保留它。和传统构造函数一样,只会占用必要的内存空间。
核心结论
不管用主构造函数还是传统构造函数,只要你没有在类的其他地方(比如其他方法、后续属性)引用data参数,编译器就不会保留这个引用。原DeviceData对象在构造完DeviceDataStatus后,只要没有其他地方引用它,就会被GC正常回收,不会造成内存开销。
所以你完全可以放心使用主构造函数的简洁写法,既保持了代码整洁,又不会有内存方面的顾虑。
内容来源于stack exchange

