404 Not Found

404 Not Found


nginx

访问修饰符与属性

访问修饰符

访问修饰符控制类成员的可访问范围,是实现封装的核心机制。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)

属性是字段的安全包装器,通过 getset 访问器控制读写逻辑。valueset 访问器的隐式参数,代表调用者赋的值。

示例

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 internalprivate protected 有什么区别?
A protected internal 是"或"关系(子类或同程序集可访问),private protected 是"与"关系(必须既是子类又在同程序集内)。
Q 自动属性和公开字段有什么区别?
A 自动属性有后备字段,可随时展开添加逻辑,支持接口实现和数据绑定,公开字段无法做到。
Q initset 的区别是什么?
A set 随时可赋值,init 只在对象初始化期间(构造函数或对象初始化器)可赋值,之后只读。
Q 只读属性 { get; } 能在构造函数外赋值吗?
A 不能。只能在构造函数中通过后备字段赋值,之后不可修改。
Q recordclass 的核心区别?
A record 默认按值比较相等性、支持 with 表达式创建副本,适合不可变数据;class 按引用比较。

📖 小节

📝 作业

  1. 创建 Temperature 类,将 celsius 字段设为 private,通过 Celsius 属性的 set 访问器限制温度不低于 -273.15,并提供 Fahrenheit 计算属性返回换算值
  2. 用自动属性创建 Book 类(TitleAuthorPrice),在 Priceset 中添加非负验证(提示:展开为完整属性)
  3. 定义 ServerConfig 类,HostPort 使用 init 访问器,并标记为 required,尝试初始化后修改观察编译错误
  4. record 定义 Point(double X, double Y),创建两个相同坐标的实例,验证 == 比较结果为 True
  5. 创建 Employee 类,Id 为只读属性(构造函数赋值),Name 使用 required initSalary 使用带验证的 set 属性,AnnualSalary 为计算属性
Web-Tutorial.com

Web-Tutorial 技术团队

由多位开发者共同维护的编程教程平台。每篇教程由对应领域的开发者编写和审核,确保内容准确可靠。如发现任何问题,欢迎向我们反馈。

100%

🙏 帮我们做得更好

我们是刚上线的编程教程站,几个人的小团队,精力有限。页面虽经检查,难免还有疏漏——链接失效、排版错乱、内容有误、语言生硬……

如果您发现了,麻烦告诉我们,我们会在收到反馈后第一时间进行修复,再次感谢您的光临 🙏