Skip to content

C# - 封装

封装是面向对象编程(OOP)的四个基本原则之一。它指的是将数据(字段或属性)与操作该数据的方法(或函数)捆绑到一个单一单元或“类”中。更重要的是,它涉及限制对对象内部状态的直接访问。这也被称为信息隐藏。

通过隐藏实现细节,封装有助于管理复杂性,提高健壮性(通过阻止外部代码将对象置于无效状态),并改善可维护性。

在 C# 中,封装主要通过结合**属性(properties)和访问修饰符(access modifiers)**来实现。我们不直接公开数据字段,而是通过属性公开它们,这些属性看起来像字段,但在 get 和 set 访问器中包含逻辑。

公开字段被认为是不良实践,因为您会失去对数据的所有控制。任何外部代码都可以将字段更改为任意值,从而可能破坏对象的内部规则(不变量)。

public class Employee
{
// 不良实践:公共字段可能被设置为无效值。
public double salary;
}
var emp = new Employee();
emp.salary = -5000; // 失控的无效状态!

带有支持字段的属性允许您添加验证逻辑。数据存储在 private(私有)字段中,并通过 public(公共)属性控制访问。

public class Employee
{
// 1. 私有支持字段,用于存储数据
private double _salary;
// 2. 公共属性,用于控制访问
public double Salary
{
get { return _salary; } // 'get' 访问器返回私有字段的值
set
{
// 'set' 访问器验证传入的 'value'
if (value < 0)
{
throw new ArgumentException("Salary cannot be negative.");
}
_salary = value;
}
}
}
var emp = new Employee();
emp.Salary = 50000; // OK
Console.WriteLine($"Employee Salary: {emp.Salary}");
try
{
emp.Salary = -100; // 抛出 ArgumentException 异常
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}

当上述代码被编译和执行时,会产生以下结果:

Employee Salary: 50000
Salary cannot be negative.

如果您不需要自定义验证逻辑,C# 提供了一种更简洁的语法,称为自动实现的属性(auto-implemented properties)。编译器会自动为您生成私有支持字段。

public class Car
{
// 编译器会创建一个隐藏的私有支持字段。
public string Model { get; set; }
public int Year { get; set; }
}
var myCar = new Car { Model = "Tesla Model 3", Year = 2023 };
Console.WriteLine($"My car: {myCar.Year} {myCar.Model}");

要创建不可变(unchangeable)对象,您可以使用 init-only setter(C# 9+ 中可用)。这确保了属性只能在对象初始化期间设置。

public class Transaction
{
public Guid TransactionId { get; init; }
public decimal Amount { get; init; }
public DateTime Timestamp { get; init; }
}
var tx = new Transaction
{
TransactionId = Guid.NewGuid(),
Amount = 19.99m,
Timestamp = DateTime.UtcNow
};
// 下面这行代码会导致编译时错误:
// tx.Amount = 25.00m; // 错误:Init-only 属性只能在初始化器中赋值。

访问修饰符定义了类及其成员的可见性。它们是强制执行封装的核心机制。

  • public:成员可从任何地方访问。无限制。
  • private:成员仅在同一类内部可访问。这是类成员的默认访问级别,提供了最高级别的封装。
  • protected:成员可在同一类内部以及派生(子)类中访问。
  • internal:成员仅在同一程序集(.dll 或 .exe)内部可访问。这是类的默认访问级别。
  • protected internal:protected 或 internal 的组合。成员可由派生类或同一程序集中的任何类访问。
  • private protected:private 和 protected 的组合。成员仅可由在同一程序集中的派生类访问。