属性与自动属性
本教程共 100 篇 · 第 34 篇 · 更新于 2026-07-31 · 约 12 分钟阅读
34. 属性
本节目标:学完你能用属性封装字段,并区分属性与字段各自的用途。
直接把字段 public 暴露出去,外部可以随意赋值,连负数年龄都能塞进来。C# 用**属性(property)**解决这个问题:它像字段一样被读写,却能在内部加逻辑、做校验。
属性包装私有字段
一个属性通常由 get 和 set 两个**访问器(accessor)**组成。get 负责读,set 里的 value 代表外部赋进来的值。字段保持 private,由属性把门。
namespace CSharpDemo;
class Person
{
private int age; // 真正的存储
public int Age
{
get { return age; }
set
{
if (value < 0) value = 0; // 负数直接归零
age = value;
}
}
}
var p = new Person();
p.Age = -5;
Console.WriteLine(p.Age); // 0,非法值被拦下
Note
value是set访问器里的关键字,代表「赋值时传进来的那个值」,类型是属性的类型。
自动属性:少写一堆样板
如果每个属性只是简单地存取值,手写私有字段很啰嗦。C# 3 起提供自动属性(auto-property):写 { get; set; },编译器自动帮你生成背后那块隐藏字段。
namespace CSharpDemo;
class Student
{
public int Id { get; set; }
public string Name { get; set; }
public int Score { get; set; }
}
var s = new Student { Id = 1, Name = "小美", Score = 88 };
Console.WriteLine($"{s.Name} 得 {s.Score} 分");
你看不见那个私有字段,但它真实存在,C# 替你管着。初学阶段绝大多数属性都用这种写法。
只读属性:只给 get
有些数据创建后就不许改,比如出生年份、订单号。只留 get(或 { get; })就成了只读属性(read-only property)。配合构造或初始化器赋值最为常见。
namespace CSharpDemo;
class Product
{
public int Id { get; }
public string Name { get; }
public Product(int id, string name)
{
Id = id;
Name = name;
}
}
var p = new Product(1, "鼠标");
// p.Id = 2; // 报错:只读属性不能改
Tip现代写法
{ get; }比老式的private set;更明确表达「真的只读」。若只想让类内部能改、外部只能读,用{ get; private set; }。
只写属性与计算属性
只写属性(只有 set)很少见,多用于敏感写入。更有用的是计算属性(computed property):get 里现算现返回,背后根本没有字段。
namespace CSharpDemo;
class Rectangle
{
public int Width { get; set; }
public int Height { get; set; }
public int Area => Width * Height; // 计算属性,无字段
}
var r = new Rectangle { Width = 4, Height = 5 };
Console.WriteLine(r.Area); // 20
=> ... 是表达式体写法,等价于 get { return Width * Height; },更短。
属性 vs 字段:怎么选
| 场景 | 用字段 | 用属性 |
|---|---|---|
| 内部临时变量 | 适合 | 没必要 |
| 对外公开的数据 | 不推荐 | 推荐 |
| 需要校验/计算 | 做不到 | 适合 |
| 将来可能加逻辑 | 改起来麻烦 | 加访问器即可 |
Note经验法则:对外公开的「数据」一律用属性。字段留给类内部自己用,或者确实是单纯的内部状态。
C# 14 的 field 关键字
C# 14 引入了 field 关键字,让你在属性里直接引用编译器生成的那个隐藏字段,既保留自动属性的简洁,又能写校验,不必再手动声明私有字段。
namespace CSharpDemo;
class Person
{
public string Name
{
get => field;
set => field = value.Trim(); // 赋值时自动去空格
}
}
var p = new Person { Name = " 阿强 " };
Console.WriteLine($"[{p.Name}]"); // [阿强]
Tip
field是 C# 14 新特性(本书基线即 C# 14)。若你的项目还停在老版本,请退回「属性包装私有字段」的传统写法。
属性的底层是什么
属性并不是字段,它编译后变成一对方法:get_X() 和 set_X(value)。你写 p.Age = 18,编译器实际调用 set_Age(18)。所以属性本质是「伪装成字段的方法」,既能像字段一样好用,又能藏着方法逻辑。
Note因为属性是方法,频繁在
get里做重计算会有性能代价。计算属性适合轻量派生(如面积),重活别塞进get。
属性还是方法:怎么选
一条实用区分:长得像「数据」、无参数、读起来很快的,用属性;需要参数、可能耗时、或有明显副作用(改了别的状态)的,用方法。
| 情况 | 用属性 | 用方法 |
|---|---|---|
obj.Name | 适合 | — |
obj.CalcTotal(items) 带参数 | — | 适合 |
| 读起来像「取一个值」 | 适合 | — |
| 会连数据库/读文件 | — | 适合 |
自动属性也能给初值
自动属性可以直接在声明处赋默认初值,等价于「字段初始化器」。
namespace CSharpDemo;
class Product
{
public string Name { get; set; } = "未命名";
public decimal Price { get; set; } = 9.99m;
}
var p = new Product();
Console.WriteLine($"{p.Name} {p.Price}"); // 未命名 9.99
访问器上的不同访问修饰符
get 和 set 可以分别设不同的可见性。常见写法是 public get; private set;:外部能读,只有类内部能改,比纯只读灵活。
namespace CSharpDemo;
class Student
{
public string Name { get; private set; }
public Student(string name) => Name = name;
}
var s = new Student("小刚");
Console.WriteLine(s.Name);
// s.Name = "x"; // 报错:set 是 private
Tip新手踩坑:把「应该只读」的属性写成
{ get; set; },外部随手改坏数据。凡是创建后不该变的,就用{ get; }或{ get; private set; }。
小心暴露集合属性
若属性返回内部集合的引用,外部拿到后能绕过你的逻辑直接改集合内容,封装就破了。常见做法是暴露只读集合(IReadOnlyList),或返回副本。
namespace CSharpDemo;
class Team
{
private List<string> members = ["小明", "小红"];
public IReadOnlyList<string> Members => members.AsReadOnly();
public void Add(string n) => members.Add(n); // 改集合走方法
}
var t = new Team();
Console.WriteLine(string.Join(",", t.Members));
// t.Members.Add("x"); // 报错:只读集合不能加
Note暴露可变集合是新手高频 bug:以为属性只读就安全,结果别人改了底层列表。返回
IReadOnlyList或IEnumerable更稳。
表达式体属性的多种形态
除了 get => ...,整个属性都能用表达式体写,包括只读、只写、甚至带逻辑。短小清晰时优先用它。
namespace CSharpDemo;
class Point
{
public int X { get; set; }
public int Y { get; set; }
public double Distance => Math.Sqrt(X * X + Y * Y); // 表达式体计算属性
}
Console.WriteLine(new Point { X = 3, Y = 4 }.Distance); // 5
自动属性背后到底是什么
public string Name { get; set; } 编译后等价于「一个私有隐藏字段 + get_Name/set_Name 方法」。你省了打字,编译器替你补齐。所以自动属性既简洁又不损失封装能力,是日常首选。
Tip该用自动属性就用自动属性;只有需要在
get/set里写校验或计算时,才退回「私有字段 + 属性」的传统写法。
只读自动属性的常见写法
只读自动属性最顺手的两种写法:一是 { get; } 配合构造或初始化器赋值;二是 { get; private set; } 让类内部后续还能改、外部只读。两者按需取用。
namespace CSharpDemo;
class Book
{
public string Isbn { get; } = "000"; // 创建后不变
public string Title { get; private set; } // 内部可改,外部只读
public Book(string title) => Title = title;
}
属性命名的约定
属性用 PascalCase(如 UserName、OrderDate),和字段(camelCase)区分开。外部调用时看到大写开头的成员,基本就是在用属性,这是 C# 的通用默契。
Tip新手容易把属性和字段的命名搞混,导致代码风格不一致。记住:对外公开的数据成员一律 PascalCase 属性,内部私有字段用 camelCase。
方法体属性与简写的选择
当 get/set 只有一行赋值或返回,用表达式体(=> ...)最清爽;需要多行逻辑、参数校验时,再用传统的花括号写法。可读性优先,不必为了短而短。
小结
属性对外像字段、对内可加逻辑,编译后是 get_X/set_X 方法,自动属性背后就是「隐藏字段 + 访问方法」。传统写法用私有字段 + get/set;日常用自动属性 { get; set; } 最省事,还能直接赋初值。只读用 { get; },想让类内能改外部只读用 { get; private set; }。计算属性用 get 现算,背后无字段。对外数据优先用属性而非公开字段,暴露集合属性时用只读接口防篡改;属性统一用 PascalCase 命名。C# 14 的 field 关键字让校验和自动属性兼得。