CodeGym /课程 /C# SELF /访问修饰符和封装的常见错误

访问修饰符和封装的常见错误

C# SELF
第 25 级 , 课程 2
可用

1. 入门

访问修饰符就像你家里的围栏和门锁:它们决定谁能进哪个房间,谁只能从钥匙孔偷看一眼。提醒一下,C#里有这些修饰符:

修饰符 谁能看到
public
所有人
private
只有当前类内部
protected
当前类和子类
internal
整个项目(程序集/assembly)
protected internal
整个项目+子类
private protected
只有本项目里的子类

封装的目标——就是把类的实现细节藏起来,这样别人就不能随便用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字段。
  • 就算只是公开读取,也用属性别用字段——以后好扩展。
评论
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION