1. 入门
访问修饰符就像你家里的围栏和门锁:它们决定谁能进哪个房间,谁只能从钥匙孔偷看一眼。提醒一下,C#里有这些修饰符:
| 修饰符 | 谁能看到 |
|---|---|
|
所有人 |
|
只有当前类内部 |
|
当前类和子类 |
|
整个项目(程序集/assembly) |
|
整个项目+子类 |
|
只有本项目里的子类 |
封装的目标——就是把类的实现细节藏起来,这样别人就不能随便用obj.Field = 99999;把你的对象搞坏,除非你自己同意。
2. 访问级别的经典错误
全都用public暴露
最常见的错误——把类的所有字段和方法都声明成public。思路大概是:“万一以后用得上呢?让别人随便访问我的成员吧!”这种做法看起来很方便,直到有人给你的对象赋了个不靠谱的值,或者开始乱用你的内部细节,结果你改代码都不敢动了。
错误示例:
public class BankAccount
{
public string Owner;
public decimal Balance;
}
现在谁都能这样写:
var acc = new BankAccount();
acc.Owner = "";
acc.Balance = -1000000M; // 突然,欠了一百万!
哪里不对?
你把所有安全和常识都丢了。就算你的银行只存树叶或者“大富翁”里的彩色纸片,随便负余额也太离谱了。
把类变成“没有结构的structure”
第二个常见错误——不仅把字段公开,连内部属性和方法也全都public,明明根本不该让外部访问。
public class Vault
{
public string DoorPIN = "1234";
public void OpenDoor()
{
// 一些魔法
}
}
现在呢?就是这样:谁都能知道PIN码,随便开你的保险箱。哪怕只是“测试保险箱”,过一个月大家都忘了这个漏洞,麻烦就来了。
正确做法:
类的接口要尽量精简和安全。别人需要用的——就public。只给自己用的——private。要给子类用的——protected。
3. 封装的错误
.NET里有个铁律:类的数据(状态)绝不能放在public字段里!都要private,或者至少用属性包起来。直接访问字段——是坏风格的典范,也是各种难查bug的根源。
为什么字段要private
字段是对象内部状态的核心。如果让外部随便访问,你就控制不了什么时候、什么值会被写进去。你的对象很容易被赋上不靠谱的值,而且以后字段格式一变,所有外部访问全挂了。
正确做法:
字段声明成private,只暴露必要的属性或方法:
public class BankAccount
{
private decimal balance;
public decimal Balance
{
get { return balance; }
private set
{
if (value < 0)
throw new ArgumentException("余额不能为负数");
balance = value;
}
}
public BankAccount(decimal initialBalance)
{
Balance = initialBalance;
}
public void Deposit(decimal amount)
{
if (amount <= 0) throw new ArgumentException("不能存0或负数!");
Balance += amount;
}
}
大错特错:不能改的数据却public set
有时候只需要get,但程序员图省事直接写public { get; set; }。结果谁都能这样:
account.Balance = 99999999; // 为啥不行?
更好的写法:
如果属性只能在类内部设置,set就加修饰符:
public decimal Balance { get; private set; }
或者,只能在对象创建时赋值(C# 14):
public decimal Balance { get; init; }
4. protected也有坑
对子类全开的大门
protected——是给子类用的好办法,但有些数据其实连子类都不该碰!比如,有些关键字段你不希望子类直接改。
例子:
public class SecureVault
{
protected string secretCode = "1234";
}
现在任何子类都能这样:
public class HackerVault : SecureVault
{
public void Hack()
{
secretCode = "0000"; // 轻松改了秘密!
}
}
建议:
protected只给真的需要子类访问的东西用。其他的都private,有需要就写protected方法。
5. internal的坑和程序集混乱
internal看起来很香:“让项目里都能访问吧”。但项目一大、加了库,冲突就来了。很多同学会不小心把本该隐藏的东西暴露在程序集里。
例子:
internal class Logger
{
internal void Write(string msg) { /* ... */ }
}
现在项目里任何地方都能用Logger.Write。要是哪天你的DLL被别的项目引用?只要你留了public,所有“内部细节”都暴露了。
建议:
技术类用internal没问题,但敏感细节还是private更保险。
6. 实战:银行应用
继续用银行应用举例,实际看看怎么容易出错、怎么改。
错误示例1:Public字段
public class BankAccount
{
public decimal Balance;
public string Owner;
}
问题:谁都能搞坏它。
用属性修正
public class BankAccount
{
private decimal _balance;
private string _owner;
public string Owner => _owner; // 只读
public decimal Balance
{
get => _balance;
private set
{
if (value < 0) throw new ArgumentException("余额不能为负数");
_balance = value;
}
}
public BankAccount(string owner, decimal initialBalance)
{
if (string.IsNullOrWhiteSpace(owner))
throw new ArgumentException("必须填写持有人姓名");
_owner = owner;
Balance = initialBalance;
}
public void Deposit(decimal amount)
{
if (amount <= 0)
throw new ArgumentException("存款金额必须为正数");
Balance += amount;
}
}
现在你要是这样写:
var acc = new BankAccount("Lena", 1000m);
acc.Balance = -700m; // 错误!set是private的。
或者这样:
acc.Owner = "Hacker"; // 编译错误,没有set
——都不行。只能通过构造函数或方法操作。
错误:只为“好看”的属性
很多人写:
public int Age { get; set; }
但年龄应该只能通过IncreaseAge方法(比如每年1月1日)来改,不能直接赋值。
7. 视觉提醒:哪些做法不行,哪些才对
graph TD
A[Public Fields] -->|任何代码| D[状态失控]
B[Private Fields + Public Methods] -->|定义清晰| E[状态受控]
F[Public Setters] -->|谁都能改| D
G[Private Setters] -->|只有类自己| E
| 做法 | 状态受保护? | 能校验吗? | 好扩展吗? |
|---|---|---|---|
| Public fields | ❌ | ❌ | ❌ |
| Properties (get/set public) | ❌ | 部分可以 | 可以,但不安全 |
| Private fields + property的set是private | ✅ | ✅ | ✅ |
8. 封装风格小贴士
- 只暴露用户真的需要的东西。
- 所有进对象的数据都要校验。
- 别图省事:属性不该外部改就把set设成private。
- 有特殊逻辑就用方法,别让外部直接访问。
- 哪怕很想,也别为了“测试”写public字段。
- 就算只是公开读取,也用属性别用字段——以后好扩展。
GO TO FULL VERSION