C# 封装(Encapsulation)详解

封装(Encapsulation)是面向对象编程(OOP)的四大特性之一,另外三个是:

  • 继承(Inheritance)
  • 多态(Polymorphism)
  • 抽象(Abstraction)

封装的核心思想:

隐藏对象内部实现细节,只向外暴露必要的功能接口。

简单理解:

  • 数据(字段)放在类内部
  • 外部不能随意修改
  • 通过属性、方法控制访问

一、为什么需要封装

假设有一个学生类:

复制代码
class Student
{
    public int Age;
}

使用:

复制代码
Student stu = new Student();

stu.Age = -100;

虽然语法正确,但年龄不可能是负数。

为了防止非法数据,需要封装。


二、使用 private 封装字段

cs 复制代码
class Student
{
    private int age;

    public void SetAge(int value)
    {
        if (value > 0)
        {
            age = value;
        }
    }

    public int GetAge()
    {
        return age;
    }
}

调用:

cs 复制代码
Student stu = new Student();

stu.SetAge(18);

Console.WriteLine(stu.GetAge());

结果:

复制代码
18

三、访问修饰符

C# 使用访问修饰符实现封装。

修饰符 访问范围
public 任何地方
private 当前类内部
protected 当前类和子类
internal 当前程序集
protected internal 当前程序集或子类

示例:

cs 复制代码
class Person
{
    public string Name;

    private int age;
}

外部:

cs 复制代码
Person p = new Person();

p.Name = "Tom"; //可以

p.age = 18;     //错误

四、属性(Property)封装

实际开发最常用。

传统写法

cs 复制代码
class Student
{
    private int age;

    public int Age
    {
        get
        {
            return age;
        }

        set
        {
            if (value > 0)
            {
                age = value;
            }
        }
    }
}

使用:

cs 复制代码
Student stu = new Student();

stu.Age = 20;

Console.WriteLine(stu.Age);

输出:

复制代码
20

五、get 和 set

get

读取属性值

cs 复制代码
Console.WriteLine(stu.Age);

执行:

cs 复制代码
get
{
    return age;
}

set

赋值属性值

复制代码
stu.Age = 18;

执行:

cs 复制代码
set
{
    age = value;
}

其中:

复制代码
value

表示赋给属性的值。

例如:

cs 复制代码
stu.Age = 18;

那么:

复制代码
value == 18

六、自动属性

如果不需要验证逻辑:

cs 复制代码
class Student
{
    public int Age { get; set; }
}

编译器自动生成私有字段。

使用:

复制代码
Student stu = new Student();

stu.Age = 18;

七、只读属性

方式1

cs 复制代码
public string Name { get; }

只能读取:

cs 复制代码
public class Student
{
    public string Name { get; }

    public Student(string name)
    {
        Name = name;
    }
}

方式2

cs 复制代码
public string Name { get; private set; }

外部只能读:

cs 复制代码
Student stu = new Student();

Console.WriteLine(stu.Name);

// stu.Name = "Tom"; 错误

类内部可修改:

复制代码
Name = "Jerry";

八、封装业务逻辑

例如银行卡余额:

cs 复制代码
class BankAccount
{
    private decimal balance;

    public decimal Balance
    {
        get { return balance; }
    }

    public void Deposit(decimal money)
    {
        if (money > 0)
        {
            balance += money;
        }
    }

    public bool Withdraw(decimal money)
    {
        if (money <= balance)
        {
            balance -= money;
            return true;
        }

        return false;
    }
}

使用:

cs 复制代码
BankAccount account = new BankAccount();

account.Deposit(1000);

account.Withdraw(300);

Console.WriteLine(account.Balance);

结果:

复制代码
700

优点:

  • 防止余额被随意修改
  • 保证业务规则正确
  • 数据更安全

九、封装与字段的区别

❌ 不推荐:

cs 复制代码
public string Name;

任何人都能直接修改:

cs 复制代码
obj.Name = "";
obj.Name = null;

✅ 推荐:

cs 复制代码
private string name;

public string Name
{
    get { return name; }

    set
    {
        if (!string.IsNullOrEmpty(value))
        {
            name = value;
        }
    }
}

十、完整案例

cs 复制代码
using System;

class Employee
{
    private string name;
    private decimal salary;

    public string Name
    {
        get { return name; }

        set
        {
            if (!string.IsNullOrWhiteSpace(value))
            {
                name = value;
            }
        }
    }

    public decimal Salary
    {
        get { return salary; }

        set
        {
            if (value >= 0)
            {
                salary = value;
            }
        }
    }

    public void ShowInfo()
    {
        Console.WriteLine($"姓名:{Name}");
        Console.WriteLine($"工资:{Salary}");
    }
}

class Program
{
    static void Main()
    {
        Employee emp = new Employee();

        emp.Name = "张三";
        emp.Salary = 8000;

        emp.ShowInfo();
    }
}

输出:

复制代码
姓名:张三
工资:8000

实际开发最佳实践

现代 C#(C# 8+)中推荐:

cs 复制代码
public class User
{
    public string UserName { get; set; }

    public int Age { get; private set; }

    public User(string userName, int age)
    {
        UserName = userName;

        if (age < 0)
            throw new ArgumentException("年龄不能小于0");

        Age = age;
    }
}

原则:

  1. 字段尽量使用 private
  2. 对外优先暴露属性(Property)
  3. set 中做数据验证
  4. 重要业务数据通过方法操作,不直接开放 set
  5. 保持"高内聚、低耦合",隐藏实现细节

一句话总结:

C# 封装 = private 字段 + Property 属性 + 方法控制访问,目的是保护数据安全、隐藏实现细节、保证对象状态合法。