首页 > 编程语言 >c#如何使用自动属性_c#自动属性的3种方式

c#如何使用自动属性_c#自动属性的3种方式

来源:互联网 2026-04-18 21:48:01

C# 自动属性:你以为的“只读”可能是个误会 谈到C#自动属性,其语法简洁是显著优势。但你是否也曾疑惑,那句简单的 public string Name { get; set; },是否默认就是“只读”的?本文将探讨自动属性中那些容易被误解的细节。 C#自动属性默认是只读的吗? 答案是否定的。像 p

C# 自动属性:你以为的“只读”可能是个误会

c#如何使用自动属性_c#自动属性的3种方式

谈到C#自动属性,其语法简洁是显著优势。但你是否也曾疑惑,那句简单的 public string Name { get; set; },是否默认就是“只读”的?本文将探讨自动属性中那些容易被误解的细节。

长期稳定更新的攒劲资源: >>>点此立即查看<<<

C#自动属性默认是只读的吗?

答案是否定的。像 public string Name { get; set; } 这样的标准写法,默认生成的是完全可读可写的属性。编译器在后台会创建一个隐藏的后备字段,以及对应的 getset 访问器。许多人感觉它“自带只读逻辑”,这其实是一种误解——属性的实际可写性,完全取决于你是否声明了 set 访问器

一个常见的错误是编译错误 CS0200(无法对只读属性赋值)。这通常是因为只编写了 get; 而遗漏了 set;,或者在C# 9及更高版本中使用了 init 访问器,却试图在对象初始化完成后再次修改它。

  • 如果仅声明 get;,则这是一个只读自动属性(只能在构造函数或 init 代码块中赋值)。
  • 如果写成 get; private set;,意味着外部不可写,但类内部可以修改。
  • 需要特别注意,init 并非 set 的简单别名。它只允许在对象初始化表达式(例如 new Person { Name = "A" })或使用 with 表达式时赋值。一旦对象创建完成,再次修改将触发错误。

C#自动属性的三种典型写法与应用场景

通常所说的“三种方式”,本质上并非语法变体,而是访问器可见性与初始化语义的不同组合。选择哪一种,关键在于你希望约束谁、以及在何时允许修改值。

  • public int Age { get; set; }:完全开放的属性。适用于数据传输对象(DTO)、配置类等无需封装复杂逻辑的场景。但这样写也意味着放弃了对数据变更的控制能力。
  • public string Code { get; private set; }:只能在构造时赋值,之后对外只读。非常适合表示标识符类的字段,如ID、订单号,可有效防止被外部代码意外覆盖。
  • public DateTime CreatedAt { get; init; } = DateTime.UtcNow;:这是C# 9引入的特性。它允许在对象创建时一次性赋值(完美支持对象初始化器语法),之后便不可更改。相比 private setinit 的语义更明确,并且与记录类型(record)的 with 表达式用法兼容。

有一个细节值得注意:init 属性不能在构造函数内部直接赋值(这会绕过 init 的语义检查),必须通过对象初始化器或 with 表达式来触发。

自动属性的后备字段会影响序列化或反射吗?

基本没有影响。编译器生成的后备字段(名称通常类似 k__BackingField)仅是编译期的实现细节。在运行时通过反射(例如调用 typeof(T).GetProperties())看到的仍然是属性本身,后备字段名不会暴露。大多数序列化器(如 JSON.NET 或 System.Text.Json)默认也按属性名进行序列化,而非字段名。

  • 以 System.Text.Json 为例,它默认会忽略带有 private setinit 的属性的 setter 调用,但只要属性有公开的 get 访问器,其值依然能被正确序列化和反序列化。
  • 当你使用 [JsonIgnore][JsonInclude] 这类特性时,作用对象是属性本身,而非其背后的隐藏字段。
  • 通过反射调用 PropertyInfo.SetValue() 方法,对于拥有 private set 的属性是有效的(因为访问器确实存在),但对于完全只读(仅有 get;)的属性,则会抛出 TargetException 异常。

何时应放弃自动属性并改用手动实现?

当属性的获取或设置需要嵌入额外逻辑时,就是自动属性该“退场”的时候。例如数据验证、触发属性变更事件、实现延迟加载、进行线程同步,或对值进行转换处理。这些场景都要求你显式声明后备字段,并完整地手动实现 getset 访问器。

  • 示例:public string Name { get => _name?.Trim(); set => _name = string.IsNullOrWhiteSpace(value) null : value.Trim(); }。这种在存取时进行修剪和空值检查的逻辑,自动属性无法胜任。
  • 在性能敏感的路径上(如高频循环中访问属性):自动属性本身没有额外开销,但如果每次 get 都要触发复杂计算,就需要考虑改用缓存策略或直接使用方法。
  • 如果需要为 getset 设置不同的访问级别(例如 public get + internal set):C# 本身支持这种写法,像 public string Id { get; internal set; } 仍属于自动属性范畴。只有当你需要在访问器中插入业务逻辑时,才必须转为手动实现。

最后,一个容易被忽略的点:自动属性的初始化表达式(例如 = DateTime.Now)是在每个实例被构造时执行的,并非静态共享、只计算一次。这一点与普通字段的初始化行为保持一致,但有时会被误认为像 const 那样。理解这一点,对于把握对象的状态至关重要。

侠游戏发布此文仅为了传递信息,不代表侠游戏网站认同其观点或证实其描述

热游推荐

更多
湘ICP备14008430号-1 湘公网安备 43070302000280号
All Rights Reserved
本站为非盈利网站,不接受任何广告。本站所有软件,都由网友
上传,如有侵犯你的版权,请发邮件给xiayx666@163.com
抵制不良色情、反动、暴力游戏。注意自我保护,谨防受骗上当。
适度游戏益脑,沉迷游戏伤身。合理安排时间,享受健康生活。