访问修饰符与属性
访问修饰符
访问修饰符控制类成员的可访问范围,是实现封装的核心机制。C# 提供六种访问级别:
| 修饰符 | 同类 | 子类(同程序集) | 子类(跨程序集) | 同程序集 | 跨程序集 |
|---|---|---|---|---|---|
public |
✅ | ✅ | ✅ | ✅ | ✅ |
private |
✅ | ❌ | ❌ | ❌ | ❌ |
protected |
✅ | ✅ | ✅ | ❌ | ❌ |
internal |
✅ | ❌ | ❌ | ✅ | ❌ |
protected internal |
✅ | ✅ | ❌ | ✅ | ✅ |
private protected |
✅ | ✅ | ❌ | ❌ | ❌ |
💡
protected internal 是"或"关系:子类或同程序集均可访问。private protected 是"与"关系:必须同时是子类且同程序集。
示例
CSHARP
public class Animal
{
public string Species;
private int health;
protected int energy;
internal string category;
public void ShowInfo()
{
Console.WriteLine($"Species: {Species}, Health: {health}, Energy: {energy}, Category: {category}");
}
public void SetHealth(int h) { health = h; }
}
public class Dog : Animal
{
public void Run()
{
energy -= 10;
category = "Mammal";
}
}
TEXT
Species: Dog, Health: 100, Energy: 90, Category: Mammal
封装原则
封装要求将字段设为 private,通过 public 属性控制读写,防止外部直接篡改内部数据。
示例
CSHARP
public class BankAccount
{
private decimal balance;
public decimal Balance
{
get { return balance; }
set
{
if (value < 0)
throw new ArgumentException("Balance cannot be negative");
balance = value;
}
}
}
CSHARP
var account = new BankAccount();
account.Balance = 1000m;
Console.WriteLine($"Balance: {account.Balance}");
TEXT
Balance: 1000
⚠️ 直接将字段设为
public 破坏封装,无法在将来添加验证逻辑。始终使用 private 字段 + public 属性。
属性(Property)
属性是字段的安全包装器,通过 get 和 set 访问器控制读写逻辑。value 是 set 访问器的隐式参数,代表调用者赋的值。
示例
CSHARP
public class Student
{
private string name;
public string Name
{
get { return name; }
set
{
if (string.IsNullOrWhiteSpace(value))
throw new ArgumentException("Name cannot be empty");
name = value;
}
}
}
CSHARP
var s = new Student();
s.Name = "Alice";
Console.WriteLine(s.Name);
TEXT
Alice
自动属性
当不需要额外逻辑时,使用自动属性 { get; set; },编译器自动生成隐藏的后备字段。
示例
CSHARP
public class Product
{
public string Name { get; set; }
public decimal Price { get; set; }
}
CSHARP
var p = new Product { Name = "Pen", Price = 2.5m };
Console.WriteLine($"{p.Name}: ${p.Price}");
TEXT
Pen: $2.5
📌 自动属性减少样板代码,需要验证时可随时展开为完整属性,不影响调用方。
init 访问器(C# 9)
init 访问器允许属性在对象初始化时赋值,之后变为只读,实现不可变对象。
示例
CSHARP
public class Config
{
public string Host { get; init; }
public int Port { get; init; }
}
CSHARP
var cfg = new Config { Host = "localhost", Port = 8080 };
Console.WriteLine($"{cfg.Host}:{cfg.Port}");
TEXT
localhost:8080
⚠️ 初始化后再次赋值会编译错误:
cfg.Port = 9090; 非法。
只读属性
只有 get 的属性为只读属性,值只能在构造函数中通过后备字段赋值。
示例
CSHARP
public class Circle
{
public double Radius { get; }
public Circle(double radius)
{
Radius = radius;
}
}
CSHARP
var c = new Circle(5.0);
Console.WriteLine($"Radius: {c.Radius}");
TEXT
Radius: 5
计算属性
计算属性没有后备字段,通过表达式体 => 动态计算返回值,不存储数据。
示例
CSHARP
public class Rectangle
{
public double Width { get; set; }
public double Height { get; set; }
public double Area => Width * Height;
public double Perimeter => 2 * (Width + Height);
}
CSHARP
var r = new Rectangle { Width = 3, Height = 4 };
Console.WriteLine($"Area: {r.Area}, Perimeter: {r.Perimeter}");
TEXT
Area: 12, Perimeter: 14
required 属性(C# 11)
required 修饰符强制调用者在初始化时必须为该属性赋值,否则编译报错。
示例
CSHARP
public class User
{
public required string Name { get; init; }
public required string Email { get; init; }
public int Age { get; init; }
}
CSHARP
var u = new User { Name = "Bob", Email = "bob@test.com" };
Console.WriteLine($"{u.Name} ({u.Email})");
TEXT
Bob (bob@test.com)
💡
required 通常与 init 搭配使用,确保不可变对象的必要字段不会被遗漏。
记录类初识
record 是 C# 9 引入的引用类型,天生支持不可变性和值语义相等性,适合纯数据对象。
示例
CSHARP
public record Person(string Name, int Age);
CSHARP
var p1 = new Person("Alice", 30);
var p2 = new Person("Alice", 30);
Console.WriteLine($"Equal: {p1 == p2}");
Console.WriteLine(p1);
TEXT
Equal: True
Person { Name = Alice, Age = 30 }
🔥 记录类的参数列表自动生成
init 只读属性,且 == 按值比较而非引用。后续课程将深入讲解。
❓ 常见问题
Q
protected internal 和 private protected 有什么区别?A
protected internal 是"或"关系(子类或同程序集可访问),private protected 是"与"关系(必须既是子类又在同程序集内)。Q 自动属性和公开字段有什么区别?
A 自动属性有后备字段,可随时展开添加逻辑,支持接口实现和数据绑定,公开字段无法做到。
Q
init 和 set 的区别是什么?A
set 随时可赋值,init 只在对象初始化期间(构造函数或对象初始化器)可赋值,之后只读。Q 只读属性
{ get; } 能在构造函数外赋值吗?A 不能。只能在构造函数中通过后备字段赋值,之后不可修改。
Q
record 和 class 的核心区别?A
record 默认按值比较相等性、支持 with 表达式创建副本,适合不可变数据;class 按引用比较。📖 小节
- 六种访问修饰符控制成员可见性:
public、private、protected、internal、protected internal、private protected - 封装原则:字段
private,属性public,通过访问器控制读写和验证 - 属性通过
get/set访问器包装字段,value为set的隐式参数 - 自动属性
{ get; set; }由编译器生成后备字段,减少样板代码 init访问器(C# 9)限制属性仅在初始化时可写,实现不可变性- 只读属性
{ get; }仅在构造函数中赋值 - 计算属性用
=>表达式动态求值,无后备字段 required修饰符(C# 11)强制初始化时必须赋值record类型天生支持不可变性和值语义相等性
📝 作业
- 创建
Temperature类,将celsius字段设为private,通过Celsius属性的set访问器限制温度不低于 -273.15,并提供Fahrenheit计算属性返回换算值 - 用自动属性创建
Book类(Title、Author、Price),在Price的set中添加非负验证(提示:展开为完整属性) - 定义
ServerConfig类,Host和Port使用init访问器,并标记为required,尝试初始化后修改观察编译错误 - 用
record定义Point(double X, double Y),创建两个相同坐标的实例,验证==比较结果为True - 创建
Employee类,Id为只读属性(构造函数赋值),Name使用required init,Salary使用带验证的set属性,AnnualSalary为计算属性



