C#中90%的字段封装场景(如模型类、DTO、配置类)用自动属性即可,它由编译器自动生成私有后备字段(如k__BackingField),语法简洁、性能与手动实现无差异;仅当需数据校验、延迟加载、通知变更或计算逻辑时才需手动实现get/set。

直接说结论:C# 中的属性不是“必须手写 get 和 set”,多数场景用自动属性就够了;手动实现只有在需要验证、计算、通知或拦截访问时才值得写。
什么时候该用自动属性,而不是手动写 get/set?
90% 的字段封装场景(比如模型类、DTO、配置类)只需自动属性。它生成私有后备字段,语法简洁,性能和手动写一样。
-
public string Name { get; set; }—— 编译器自动生成字段k__BackingField - 加
init修饰符(public int Age { get; init; })可实现构造后只读,比readonly字段更灵活 - 不能在
get或set里加逻辑,但能加访问修饰符:public string Id { get; private set; } - 注意:自动属性不能用于
ref返回、也不能在get中 throw 异常——这些必须手动实现
手动实现 get/set 的真实使用场景有哪些?
手动写不是为了“看起来更专业”,而是解决具体问题:数据校验、延迟加载、属性变更通知、兼容旧字段逻辑等。
- 校验赋值:比如
set中检查value > 0,不合法就 throwArgumentException - 触发副作用:在
set中调用OnPropertyChanged(nameof(Title))(INotifyPropertyChanged 场景) - 计算属性:如
get { return _firstName + " " + _lastName; },不设set就是只读 - 延迟初始化:第一次
get才创建耗资源对象(注意线程安全,别直接用Lazy就完事)
常见错误:把属性当成方法来设计
属性语义是“数据”,不是“动作”。一旦你在 get 里做 IO、网络请求、复杂计算或修改其他状态,就违背了属性的设计契约。
- 错误示例:
public string JsonData { get { return File.ReadAllText("config.json"); } }—— 每次访问都读文件,且没缓存 - 正确做法:要么改用方法(
string LoadJsonData()),要么在get中加缓存 + 线程保护 - 调试时发现属性求值多次?可能被 IDE 预览、WPF 绑定、序列化反复触发 —— 这类副作用会让问题难以复现
- 别在
set中调用虚方法或引发事件链(如 A.set → 触发 B.OnChanged → B.set → …),容易栈溢出
性能与兼容性要注意的几个点
属性看似简单,但在高频调用、跨程序集、AOT 编译(如 .NET Native / iOS)下,细节会影响行为。
- 自动属性在 IL 层就是普通方法(
get_Name/set_Name),JIT 可以内联,性能无差别 - 手动实现时,避免在
get中分配新对象(如return new List),这会误导调用方以为返回的是副本(items); - 如果类要被 JSON 序列化(System.Text.Json),默认只序列化 public get/set 属性;若用了
private set,需显式配JsonSerializerOptions.PropertyNamingPolicy = null或加[JsonPropertyName] - .NET 6+ 支持
record类型,它的with表达式依赖属性的get和init,别把关键字段藏在私有字段里
最常被忽略的一点:属性访问器里的异常不会被 WPF 或 ASP.NET Core Model Binding 自动捕获为验证错误,它们会直接崩掉绑定流程或返回 500。真要抛异常,得配合 IValidatableObject 或数据注解([Required])来处理。










