C# OOP 精通 — 从测验答案到生产级思维模型
Source: Dev.to

大多数 C# 面试不会让你构建完整的企业系统。
相反,它们会悄悄地测试你是否真正理解 OOP 基础背后的 思维模型:
- 定义类 与 创建对象 有什么区别?
- 为什么方法代表 行为?
- C# 能否从 多个类 继承?
- 为什么要使用构造函数(以及何时更倾向于对象初始化器)?
- 访问修饰符到底控制什么?
- 为什么在现代 C# 中
record类型是 “特殊”的?
在本文中,我们将把测验式的问题转化为 可在生产环境中复用的模式,帮助你在以下场景中使用:
- 代码审查
- 架构讨论
- 技术面试
- 实际 .NET 服务
TL;DR — 您将学习
- 类型 vs. 实例 思维模型(
classvs.new) - 为什么方法是 行为,而不仅仅是“函数”
- 实际规则:单类继承,多接口
- 构造函数、对象初始化器和 不变式
- 使用属性进行封装:
get; set;(以及高级开发者如何限制 setter) - 访问修饰符:不是“是否存在”,而是 谁可以访问它
protected的真实情况(以及何时它成为设计异味)record存在的原因:值相等、不可变性和数据建模
1) “哪个关键字创建对象?” — 定义 vs. 实例化
测验版本
“在 C# 中,用什么关键字或指令来创建对象?”
✅ 答案: new
高级思维模型
class定义一种类型(蓝图)new创建实例(托管堆上的运行时对象,由构造函数初始化)
public class Person
{
public string Name { get; }
public Person(string name) => Name = name;
}
var p = new Person("Carlos"); // ✅ 创建对象(实例)
面试金句
“
class定义蓝图;new构建真实实例并运行构造函数。”
2) “方法在类内部定义了什么?” — 行为
测验版本
“方法在类内部定义了什么?”
✅ 答案: 行为
为什么这在生产环境中很重要
- Properties/fields 表示 state。
- Methods 表示 behavior——改变该状态的 rules。
public sealed class BankAccount
{
public decimal Balance { get; private set; }
public void Deposit(decimal amount)
{
if (amount > 0)
{
Balance += amount;
}
// else ignore or throw
}
}
“Methods encode business rules. Properties hold data. Behavior protects invariants.”
3) “C# 只允许实现一个接口” — 错误
测验版本
“C# 只允许实现一个接口。”
✅ 答案: 错误
真正规则(必须记住)
- ✅ 一个类可以实现 多个接口。
- ❌ 一个类只能继承 一个基类。
public sealed class ExportService : IDisposable, IAsyncDisposable, IHostedService
{
public void Dispose() { /*...*/ }
public ValueTask DisposeAsync() => ValueTask.CompletedTask;
public Task StartAsync(CancellationToken ct) => Task.CompletedTask;
public Task StopAsync(CancellationToken ct) => Task.CompletedTask;
}
面试金句
“类采用单继承,接口采用多继承。”
Source: …
4) “我们如何在创建时初始化属性?” — 构造函数(及其相关)
测验版
“我们如何在对象创建时初始化其属性?”
✅ 答案: 使用构造函数
高级细节:构造函数 vs. 对象初始化器
两者都可以初始化值,但它们的目标不同。
✅ 构造函数 = 强制不变量
当对象必须以有效状态创建时使用。
public sealed class Order
{
public Guid Id { get; }
public string CustomerEmail { get; }
public Order(Guid id, string customerEmail)
{
if (string.IsNullOrWhiteSpace(customerEmail))
throw new ArgumentException("Email required.", nameof(customerEmail));
Id = id;
CustomerEmail = customerEmail;
}
}
✅ 对象初始化器 = 可选字段的易用性
非常适合 DTO 或选项袋。
var opts = new HttpClientOptions
{
Timeout = TimeSpan.FromSeconds(10),
Retries = 3
};
面试金句
“构造函数用于建立不变量。对象初始化器则为可选配置提供便利。”
5) “一个类可以作为另一个类的属性类型” — 组合
✅ 答案: True
这是一种组合:一种 has‑a(拥有)关系(通常比继承更好)。
public sealed class Address
{
public string Street { get; init; } = "";
}
public sealed class Person
{
public Address Address { get; init; } = new();
}
为什么资深开发者更倾向于组合
- 继承会导致紧耦合。
- 组合保持边界更清晰,并且支持替换。
面试金句
“更倾向于使用组合:它比深层继承链更容易重构和测试。”
6) “访问修饰符定义 …” (原文内容被截断)
测验版本
“访问修饰符定义成员的可见性。”
✅ 答案: True
实际控制的内容
| 修饰符 | 可访问范围 |
|---|---|
public | 任意位置 |
internal | 同一程序集 |
protected | 派生类型(任意程序集) |
protected internal | 派生类型 或 同一程序集 |
private protected | 同一程序集中的派生类型 |
private | 仅限包含类型 |
高级视角
- 封装 关注的是 谁可以触及 成员,而不是 成员是否存在。
- 过度公开成员(
public)可能成为设计异味。 - 应优先使用最严格的修饰符,只要能满足实际使用需求。
public abstract class Shape
{
// Only derived types can read/write the radius.
protected double Radius { get; set; }
// Only this class can change the ID after construction.
public Guid Id { get; private set; } = Guid.NewGuid();
// Internal helper used by the assembly but hidden from consumers.
internal void Validate() { /*...*/ }
}
面试金句
“访问修饰符是一种关于 谁 可以与成员交互的契约,而不是对成员存在的声明。”
7) “为什么会有 record 类型?” — 值相等与不可变性
测验版
“在现代 C# 中,
record类型有什么特别之处?”
✅ 答案: 值相等、内置不可变性以及简洁的数据建模
关键好处
- 自动生成基于 值的
Equals/GetHashCode。 - 使用
with表达式 进行非破坏性修改。 - 对 DTO 和 领域模型 提供简洁语法。
public record PersonDto(string FirstName, string LastName);
var p1 = new PersonDto("Alice", "Smith");
var p2 = p1 with { LastName = "Johnson" }; // 创建一个新的不可变实例
bool same = p1 == new PersonDto("Alice", "Smith"); // true – 值相等
面试金句
“当你需要不可变、值语义的数据载体时使用
record;对具有身份语义的实体则避免使用。”
结束语
理解这些 OOP 基础背后的 原因 能帮助你:
- 编写能够 强制不变量 并 保护状态 的代码。
- 正确选择 继承 vs. 组合 的策略。
- 在 代码评审 和 面试 中清晰沟通。
掌握这些思维模型,你就能从回答测验题目转向构建健壮、生产级的 .NET 应用。
C# 面试快速检查
1️⃣ “一个类拥有属性和方法的类拥有” — 错误
正确答案: 错误
现实情况
访问修饰符定义的是 可见性,而不是成员是否存在。
public sealed class User
{
public string Email { get; private set; } = "";
private string PasswordHash { get; set; } = "";
}
面试金句
“访问修饰符回答的是‘谁可以触碰这个成员?’而不是‘它是否存在?’”
2️⃣ “C# 能从多个类继承吗?” — 不能
正确答案: 只能继承一个类
public class Employee : Person // ✅
{
}
我们在 C# 中如何模拟“多重继承”
- 接口 用于能力抽象
- 组合 用于行为复用
- 装饰器 与 委托 用于横切关注点
面试金句
“C# 避免了多类继承——接口 + 组合提供了灵活性,同时没有菱形继承的问题。”
3️⃣ “哪个符号给属性赋值?” — =
正确答案: =
person.Name = "Juan"; // 赋值运算符
高级开发者关注这一点,因为赋值往往是 不变量被破坏 的地方。
因此你经常会看到:
private set;init;- 用于安全变更的成员方法
4️⃣ protected 访问范围 — 正确(但需谨慎使用)
正确答案: 正确
protected 的含义是:
- 在声明它的类型内部可访问
- 在派生类型内部可访问
public class Base
{
protected int Value;
}
public class Derived : Base
{
public void Set() => Value = 10; // ✅ 允许
}
高级警告
protected可能导致“泄漏的继承”设计,派生类依赖于脆弱的内部细节。
使用场景:
- 基类明确设计为可扩展
- 你能够控制继承层次结构
- 你拥有稳定且有文档说明的模板方法模型
5️⃣ 记录(record)因为值相等而重要 — 正确
正确答案: 正确
record 存在的原因
记录类型专为 数据建模 而设计:
- 基于值的相等性
- 简单的不可变性
- 结构化比较
非常适合 DDD 中的消息 / 事件 / DTO。
public record Person(string Name, int Age);
var p1 = new Person("Ana", 30);
var p2 = new Person("Ana", 30);
Console.WriteLine(p1 == p2); // ✅ true (值相等)
面试金句
“记录用于具有值语义的数据——非常适合 DTO 和领域事件。”
6️⃣ 接口关键字 — interface
public interface ILogger
{
void Log(string message);
}
7️⃣ 类关键字 — class
public class Order { }
8️⃣ “面向对象的基本概念” — 类和对象
正确答案: class 和 object(类和对象)
其他概念(继承、多态、抽象、封装)都是建立在这一基础之上的。
9️⃣ C# 中的多态 — 重写(以及接口)
正确答案: Overwrite(覆盖)
public abstract class Animal
{
public abstract string Speak();
}
public sealed class Dog : Animal
{
public override string Speak() => "Bark";
}
🔟 抽象声明 — 正确
正确答案: 正确
抽象定义 做什么,而不指定 怎么做:
- 接口
- 抽象类
1️⃣1️⃣ “属性需要考虑哪些因素?” — 访问级别、类型、名称
正确答案: A. 访问级别、类型和名称
public string Name { get; private set; } = "";
1️⃣2️⃣ 封装关键字 — get; set;
正确答案: get; set;
高级升级
封装并不是“到处都用 public get/set”。它是对变更的控制。
public sealed class Customer
{
public string Email { get; private set; }
public Customer(string email)
{
// 验证并规范化
Email = email.Trim().ToLowerInvariant();
}
public void ChangeEmail(string newEmail)
{
Email = newEmail.Trim().ToLowerInvariant();
}
}
It looks like the text you’d like translated didn’t come through. Could you please resend the content (the part you want translated to Simplified Chinese)? Once I have the text, I’ll provide the translation while preserving the source link, formatting, and any code blocks as you requested.
最终思考 — 为什么这些“基本”答案很重要
- SOLID(封装、抽象、可替换性)
- Clean Architecture(边界、组合)
- Dependency Injection(接口、对象图创建)
- Maintainability(不变量和受控变更)
✍️ 作者 Cristian Sifuentes — 构建弹性 .NET 系统,教导团队如何思考面向对象编程、边界以及清晰架构。