C# 封装
访问修饰符、属性、以及为什么要用属性而不是公共字段。
Key Points字段私有、属性公开——这样以后加校验或改实现不会破坏调用方。
访问修饰符
| Modifier | Visible to |
|---|---|
public | 任何代码 |
private | 当前类(默认) |
protected | 当前类及派生类 |
internal | 当前程序集 |
protected internal | 程序集内 或 派生类 |
private protected | 程序集内的派生类 |
C#
class Account
{
private decimal _balance; // 只有自己能碰
public string Owner { get; } // 外部只读
internal int Id; // 同程序集可见
public Account(string owner, decimal initial)
{
Owner = owner;
_balance = initial;
}
public decimal Balance => _balance;
public void Deposit(decimal amount)
{
if (amount <= 0) throw new ArgumentException("金额必须为正");
_balance += amount;
}
}
var a = new Account("张三", 100);
a.Deposit(50);
Console.WriteLine($"{a.Owner} {a.Balance}");
// a._balance = 999; // 编译错误:私有Output
张三 150属性
C#
class User
{
private string _name = "";
// 完整属性:带校验
public string Name
{
get => _name;
set
{
if (string.IsNullOrWhiteSpace(value))
throw new ArgumentException("名字不能为空");
_name = value.Trim();
}
}
// 自动属性
public int Age { get; set; }
// 只读自动属性(只能在构造函数赋值)
public string Id { get; }
// 计算属性
public bool IsAdult => Age >= 18;
// 初始化器
public List<string> Tags { get; } = new();
public User(string id) => Id = id;
}
var u = new User("u1") { Name = " 李四 ", Age = 20 };
Console.WriteLine($"{u.Id} {u.Name} {u.IsAdult}");Output
u1 李四 TrueA property is sugar for get_X()/set_X() methods, so it costs nothing (except auto-properties, which have a hidden field) and can later gain logic without breaking binary compatibility.
为什么不用公共字段
C#
// 反例:公共字段,以后想加校验就得改所有调用方
class Bad
{
public int Age; // 谁都能写 -5
}
// 正例:属性,改实现不影响调用方
class Good
{
private int _age;
public int Age
{
get => _age;
set => _age = value < 0 ? 0 : value;
}
}
var g = new Good { Age = -5 };
Console.WriteLine(g.Age);Output
0Turning a public field into a property is a breaking change for both source and binary. Start with a property even if it is just
{ get; set; } — the most basic and most ignored C# rule.init 与 required
C#
class Order
{
public required string Id { get; init; } // 必须初始化
public decimal Amount { get; init; } // 只能在初始化时赋值
public DateTime Created { get; init; } = DateTime.Now;
}
var o = new Order { Id = "A001", Amount = 99.9m };
Console.WriteLine($"{o.Id} {o.Amount}");
// o.Amount = 1; // 编译错误:init 只能在初始化器里赋值Output
A001 99.9init allows assignment only in an object initializer — more flexible than readonly, safer than set. required forces the caller to assign, checked at compile time.
访问修饰符
| 修饰符 | 可见范围 |
|---|---|
public | 任何代码 |
private | 当前类(默认) |
protected | 当前类及派生类 |
internal | 当前程序集 |
protected internal | 程序集内 或 派生类 |
private protected | 程序集内的派生类 |
C#
class Account
{
private decimal _balance; // 只有自己能碰
public string Owner { get; } // 外部只读
internal int Id; // 同程序集可见
public Account(string owner, decimal initial)
{
Owner = owner;
_balance = initial;
}
public decimal Balance => _balance;
public void Deposit(decimal amount)
{
if (amount <= 0) throw new ArgumentException("金额必须为正");
_balance += amount;
}
}
var a = new Account("张三", 100);
a.Deposit(50);
Console.WriteLine($"{a.Owner} {a.Balance}");
// a._balance = 999; // 编译错误:私有Output
张三 150属性
C#
class User
{
private string _name = "";
// 完整属性:带校验
public string Name
{
get => _name;
set
{
if (string.IsNullOrWhiteSpace(value))
throw new ArgumentException("名字不能为空");
_name = value.Trim();
}
}
// 自动属性
public int Age { get; set; }
// 只读自动属性(只能在构造函数赋值)
public string Id { get; }
// 计算属性
public bool IsAdult => Age >= 18;
// 初始化器
public List<string> Tags { get; } = new();
public User(string id) => Id = id;
}
var u = new User("u1") { Name = " 李四 ", Age = 20 };
Console.WriteLine($"{u.Id} {u.Name} {u.IsAdult}");Output
u1 李四 True属性本质上是 get_X() / set_X() 两个方法的语法糖。所以它不占额外内存(自动属性除外,背后有个隐藏字段),而且以后改成带逻辑的版本不破坏二进制兼容。
为什么不用公共字段
C#
// 反例:公共字段,以后想加校验就得改所有调用方
class Bad
{
public int Age; // 谁都能写 -5
}
// 正例:属性,改实现不影响调用方
class Good
{
private int _age;
public int Age
{
get => _age;
set => _age = value < 0 ? 0 : value;
}
}
var g = new Good { Age = -5 };
Console.WriteLine(g.Age);Output
0公共字段改成属性是破坏性变更(源码和二进制都不兼容)。所以从一开始就用属性——即使现在只是
{ get; set; }。这是 C# 里最基础也最常被忽视的规范。init 与 required
C#
class Order
{
public required string Id { get; init; } // 必须初始化
public decimal Amount { get; init; } // 只能在初始化时赋值
public DateTime Created { get; init; } = DateTime.Now;
}
var o = new Order { Id = "A001", Amount = 99.9m };
Console.WriteLine($"{o.Id} {o.Amount}");
// o.Amount = 1; // 编译错误:init 只能在初始化器里赋值Output
A001 99.9init 让属性只能在对象初始化器里赋值,之后只读——比 readonly 灵活(能用于对象初始化器),比 set 安全。required 强制调用方必须赋值,编译期检查。