C# 属性(Property)
自动属性、`init`、`required`、计算属性、以及属性 vs 字段。
Key Points属性是方法不是字段,可以加逻辑、控制可见性、延迟计算。
四种写法
C#
class Demo
{
// 1. 自动属性:编译器生成隐藏字段
public int A { get; set; }
// 2. 只读自动属性:只能在构造函数赋值
public int B { get; }
// 3. init:只能在对象初始化器赋值
public int C { get; init; }
// 4. 完整属性:带逻辑
private int _d;
public int D
{
get => _d;
set => _d = value < 0 ? 0 : value;
}
public Demo(int b) => B = b;
}
var x = new Demo(2) { A = 1, C = 3, D = -5 };
Console.WriteLine($"{x.A} {x.B} {x.C} {x.D}");Output
1 2 3 0计算属性
C#
class Order
{
public decimal UnitPrice { get; set; }
public int Quantity { get; set; }
public decimal TaxRate { get; set; } = 0.13m;
// 每次访问都重新算
public decimal Subtotal => UnitPrice * Quantity;
public decimal Tax => Subtotal * TaxRate;
public decimal Total => Subtotal + Tax;
}
var o = new Order { UnitPrice = 100m, Quantity = 3 };
Console.WriteLine($"{o.Subtotal} {o.Tax} {o.Total}");Output
300 39.00 339.00A computed property has no backing field; => is shorthand for get { return ...; }. It always reflects current state — change Quantity and Total follows.
访问修饰符
C#
class Account
{
// 外部只读,内部可写
public decimal Balance { get; private set; }
// 只在派生类可写
public string Owner { get; protected set; } = "";
// get 和 set 分别控制
private string _name = "";
public string Name
{
get => _name;
internal set => _name = value.Trim();
}
public void Deposit(decimal amount) => Balance += amount;
}
var a = new Account();
a.Deposit(100);
Console.WriteLine(a.Balance);
// a.Balance = 1; // 编译错误:set 是 privateOutput
100required 与 init
C#
class Product
{
public required string Sku { get; init; } // 必须赋值
public required string Name { get; init; }
public decimal Price { get; init; }
public DateTime Created { get; init; } = DateTime.Now;
}
var p = new Product { Sku = "A001", Name = "键盘", Price = 199m };
Console.WriteLine($"{p.Sku} {p.Name} {p.Price}");
// var bad = new Product { Sku = "A002" };
// 编译错误:Required member 'Name' must be setOutput
A001 键盘 199required + init replaces long constructor parameter lists: callers use an object initializer, missing fields fail at compile time, and the object stays immutable afterwards.
延迟初始化
C#
class Report
{
private List<string>? _cache;
// 第一次访问才计算
public List<string> Rows => _cache ??= Load();
private static List<string> Load()
{
Console.WriteLine("(加载中)");
return new List<string> { "row1", "row2" };
}
}
var r = new Report();
Console.WriteLine("创建完成");
Console.WriteLine(r.Rows.Count);
Console.WriteLine(r.Rows.Count); // 不再加载Output
创建完成
(加载中)
2
2??= is the shortest lazy init: compute only when null. Three lines shorter than the if version with identical semantics.
属性 vs 字段
| Aspect | Field | Property |
|---|---|---|
| 本质 | 变量 | get/set 方法 |
| 能否加逻辑 | 不能 | 能 |
| 能否虚方法 | 不能 | 能(virtual) |
| 接口中 | 不能 | 能 |
| 数据绑定 | 不支持 | 支持(WPF/Blazor) |
| 序列化 | 通常不序列化 | 常见目标 |
Changing a public field to a property is a breaking change for both source and binary — start with a property. Public fields are only acceptable as
internal/private implementation details.四种写法
C#
class Demo
{
// 1. 自动属性:编译器生成隐藏字段
public int A { get; set; }
// 2. 只读自动属性:只能在构造函数赋值
public int B { get; }
// 3. init:只能在对象初始化器赋值
public int C { get; init; }
// 4. 完整属性:带逻辑
private int _d;
public int D
{
get => _d;
set => _d = value < 0 ? 0 : value;
}
public Demo(int b) => B = b;
}
var x = new Demo(2) { A = 1, C = 3, D = -5 };
Console.WriteLine($"{x.A} {x.B} {x.C} {x.D}");Output
1 2 3 0计算属性
C#
class Order
{
public decimal UnitPrice { get; set; }
public int Quantity { get; set; }
public decimal TaxRate { get; set; } = 0.13m;
// 每次访问都重新算
public decimal Subtotal => UnitPrice * Quantity;
public decimal Tax => Subtotal * TaxRate;
public decimal Total => Subtotal + Tax;
}
var o = new Order { UnitPrice = 100m, Quantity = 3 };
Console.WriteLine($"{o.Subtotal} {o.Tax} {o.Total}");Output
300 39.00 339.00计算属性没有背后字段,=> 就是 get { return ...; } 的简写。它总是反映最新状态——改了 Quantity,Total 自动变。
访问修饰符
C#
class Account
{
// 外部只读,内部可写
public decimal Balance { get; private set; }
// 只在派生类可写
public string Owner { get; protected set; } = "";
// get 和 set 分别控制
private string _name = "";
public string Name
{
get => _name;
internal set => _name = value.Trim();
}
public void Deposit(decimal amount) => Balance += amount;
}
var a = new Account();
a.Deposit(100);
Console.WriteLine(a.Balance);
// a.Balance = 1; // 编译错误:set 是 privateOutput
100required 与 init
C#
class Product
{
public required string Sku { get; init; } // 必须赋值
public required string Name { get; init; }
public decimal Price { get; init; }
public DateTime Created { get; init; } = DateTime.Now;
}
var p = new Product { Sku = "A001", Name = "键盘", Price = 199m };
Console.WriteLine($"{p.Sku} {p.Name} {p.Price}");
// var bad = new Product { Sku = "A002" };
// 编译错误:Required member 'Name' must be setOutput
A001 键盘 199required + init 是替代「构造函数一长串参数」的现代写法:调用方用对象初始化器,漏了必填项编译期就报错,之后又不可变。
延迟初始化
C#
class Report
{
private List<string>? _cache;
// 第一次访问才计算
public List<string> Rows => _cache ??= Load();
private static List<string> Load()
{
Console.WriteLine("(加载中)");
return new List<string> { "row1", "row2" };
}
}
var r = new Report();
Console.WriteLine("创建完成");
Console.WriteLine(r.Rows.Count);
Console.WriteLine(r.Rows.Count); // 不再加载Output
创建完成
(加载中)
2
2??= 在这里就是惰性初始化的最短写法:为 null 才算。比 if (_cache == null) _cache = Load(); 少三行,语义一样。
属性 vs 字段
| 对比项 | 字段 | 属性 |
|---|---|---|
| 本质 | 变量 | get/set 方法 |
| 能否加逻辑 | 不能 | 能 |
| 能否虚方法 | 不能 | 能(virtual) |
| 接口中 | 不能 | 能 |
| 数据绑定 | 不支持 | 支持(WPF/Blazor) |
| 序列化 | 通常不序列化 | 常见目标 |
公共字段改属性是破坏性变更(源码和二进制都不兼容),所以从第一行代码就该用属性。唯一适合公共字段的场景是
internal 或 private 的实现细节。