C#基础08-面向对象
1、面向对象三大特性
(1)封装(Encapsulation)
- 核心思想:隐藏对象内部细节,仅暴露必要接口,通过访问修饰符控制数据安全。
- 访问修饰符
private
:仅本类内可访问(默认修饰符)protected
:本类及子类可访问internal
:同一程序集内可访问public
:全局可访问protected internal
:protected
+ internal
权限之和
- 实现方式
- 字段封装:私有字段 + 公共属性(如
private string _name; public string Name { get; set; }
) - 方法封装:仅公开必要方法,隐藏内部逻辑(如数据验证)
public class BankAccount {private double _balance; public void Deposit(double amount) { if (amount > 0) _balance += amount;}
}
- 设计原则
- 单一职责原则(SRP):一个类只负责一项功能
- 开闭原则(OCP):对扩展开放,对修改关闭
(2)继承(Inheritance)
- 核心思想:子类复用父类特性,并扩展新功能。
- 关键规则
- 单继承:C# 仅支持单继承(如
class Dog : Animal
) - 里氏替换原则:子类对象可替代父类对象(如
Animal animal = new Dog();
) - 构造方法顺序:先执行父类构造方法,再执行子类
- 成员继承:子类继承父类所有成员(包括私有成员,但不可直接访问)
- 实现方式
- 类继承:子类继承父类属性和方法
- 抽象类与接口
- 抽象类(
abstract
):包含未实现方法,需子类重写 - 接口(
interface
):纯契约定义,无实现
public class Animal {public virtual void Speak() => Console.WriteLine("Animal sound");
}
public class Dog : Animal {public override void Speak() => Console.WriteLine("Bark!");
}
- 禁用继承:用
sealed
修饰类(如 sealed class FinalClass
)
(3)多态(Polymorphism)
- 核心思想:同一操作作用于不同对象时,表现出不同行为。
- 编译时多态(静态)
public class Calculator {public int Add(int a, int b) => a + b;public double Add(double a, double b) => a + b;
}
- 方法隐藏:子类用 `new` 隐藏父类方法(非重写)
public class Parent { public void Print() => Console.WriteLine("Parent"); }
public class Child : Parent { public new void Print() => Console.WriteLine("Child");
}
- 运行时多态(动态)
- 虚方法重写(
virtual
+ override
):
Animal animal = new Dog();
animal.Speak();
- 抽象方法重写(`abstract`):父类声明抽象方法,子类强制实现
- 接口多态:通过接口引用调用不同实现
interface IDrawable { void Draw(); }
class Circle : IDrawable { public void Draw() => Console.WriteLine("Drawing circle"); }
class Square : IDrawable { public void Draw() => Console.WriteLine("Drawing square"); }IDrawable shape = new Circle();
shape.Draw();
- 多态优势
- 提高代码扩展性:新增子类无需修改父类逻辑
- 增强灵活性:同一接口处理不同对象(如
List<Animal>
存储多种动物)
(4)三大特性对比表
特性 | 核心目的 | 关键实现 | 设计原则 |
---|
封装 | 数据安全与简化使用 | 访问修饰符 + 属性/方法封装 | 单一职责原则 (SRP) |
继承 | 代码复用与层次化设计 | 类继承 (: ) + 抽象类/接口 | 里氏替换原则 (LSP) |
多态 | 接口统一与行为多样化 | 重载/重写 (override )/接口实现 | 依赖倒置原则 (DIP) |
(5)实战建议
- 封装场景:敏感数据(如密码)设为
private
,通过公共方法访问。 - 继承陷阱:避免过度继承导致“脆弱基类问题”,优先使用组合。
- 多态优化:
- 用
virtual
/override
替代 new
实现真正多态 - 接口多态适用于跨组件协作(如插件系统)
- SOLID 补充:结合开闭原则(OCP)和接口隔离原则(ISP)提升设计质量。
2、访问修饰符
(1)访问范围从宽到严
public
- 权限:无限制访问
- 适用对象:类、成员(字段/方法/属性)
- 场景:跨程序集公开API
public class Logger { public string LogPath; }
protected internal
- 权限:
protected
+ internal
的并集(当前程序集 或 任意派生类) - 场景:组件库中供派生类扩展的接口
protected internal void Initialize() { }
internal
- 权限:仅当前程序集内可访问(默认类访问级别)
- 场景:模块内部实现隐藏
internal class Cache { }
protected
- 权限:当前类 及派生类(不限程序集)
- 场景:设计可继承框架
protected virtual void OnStart() { }
private
- 权限:仅当前类内部(成员默认访问级别)
- 场景:封装内部状态
private string _connectionKey;
(2)关键规则与限制
规则类型 | 说明 | 示例 |
---|
类修饰符限制 | 类仅支持 public 或 internal (默认internal ) | internal class Utility → 禁止跨程序集实例化 |
成员可访问性 | 成员不能比其所属类更开放(如 internal 类中不能声明 public 成员) | ❌ internal class A { public void Foo() } → 编译错误 |
接口/显式实现 | 接口成员或显式接口实现禁止使用访问修饰符 | interface IDevice { void Run(); } → 隐含 public |
跨程序集继承 | 派生类访问基类protected 成员时,需遵守基类程序集的internal 限制 | 基类internal 成员 → 即使protected ,外部程序集派生类仍不可访问 |
(3)权限范围对比表
修饰符 | 当前类 | 同程序集派生类 | 同程序集非派生类 | 跨程序集派生类 | 跨程序集非派生类 |
---|
public | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ |
protected internal | ✔️ | ✔️ | ✔️ | ✔️ | ❌ |
internal | ✔️ | ✔️ | ✔️ | ❌ | ❌ |
protected | ✔️ | ✔️ | ❌ | ✔️ | ❌ |
private | ✔️ | ❌ | ❌ | ❌ | ❌ |
- 设计原则:
- 优先用
private
封装细节,通过公共方法暴露功能 - 库开发时,用
internal
隐藏非公开API,protected
预留扩展点 - 避免滥用
public
(增大耦合风险)和 protected internal
(增加理解成本)
(4)常见误区与陷阱
- 默认权限混淆
- 类默认
internal
,成员默认 private
→ 未显式声明时易导致访问错误
- 跨程序集保护失效
public class Base { protected int Key; }
class Derived : Base { void UseKey() { Console.Write(Key); } static void LeakKey(Base b) { Console.Write(b.Key); }
}
private protected
(C# 7.2+) - 限制比
protected internal
更严:仅允许 同一程序集中的派生类
private protected string _secret;
(5)总结与最佳实践
- 安全优先:成员起始设为
private
,按需放宽权限 - 组件设计:
public
→ 稳定接口internal
→ 内部实现protected
→ 扩展点
- 代码审查:检查所有
public
成员是否必要,减少全局暴露
3、继承
(1)基础概念
- 定义与作用
- 继承:子类(派生类)基于父类(基类)创建,自动获得父类非私有成员(字段/方法/属性)
- 目的:实现代码复用、逻辑分层(如将通用字段
ID
/CreateTime
提取至基类) - 特性:
- C# 仅支持单继承(一个子类只能有一个直接父类)
- 未指定父类时,默认继承
System.Object
- 语法结构
class 父类 { }
class 子类 : 父类 { }
(2)关键特性与规则
- 构造函数执行顺序:
- 子类实例化时按以下顺序执行构造函数
- 若无显式构造函数,系统自动生成无参构造方法

关键字 | 子类访问权限 | 示例 |
---|
public | ✅ 可直接访问 | 子类.字段名 |
protected | ✅ 可直接访问 | 子类.字段名 |
private | ❌ 不可访问 | 需通过父类公共方法间接访问 |
internal | 同程序集内可访问 | 跨类但同项目 |
- 方法重写与隐藏:重写影响多态行为;隐藏仅覆盖当前子类。
方式 | 关键字 | 特点 | 示例 |
---|
重写 | override | 修改父类虚方法逻辑(需父类方法标记 virtual ) | public override void Method() |
隐藏 | new | 创建与父类同名的新方法,隐藏继承的原始方法 | public new void Method() |
(3)进阶特性与注意事项
- 多层继承传递性:若
C → B → A
,则 C
继承 B
和 A
的所有非私有成员
class Animal {}
class Mammal : Animal {}
class Dog : Mammal {}
- 避免多重继承歧义:C# 不支持多父类继承(如
class C : A, B
会报错),需通过接口实现多重能力。 - 装箱与拆箱影响:继承链中值类型与引用类型转换可能引发性能损耗(如
object obj = new Dog();
)。
(4)实战示例
class Phone {public string Color { get; set; }public double Price { get; set; }public Phone(string color, double price) {Color = color; Price = price;}
}
class CellPhone : Phone {public string MaxBatteryLife { get; set; }public CellPhone(string color, double price, string battery) : base(color, price) {MaxBatteryLife = battery;}
}
var myPhone = new CellPhone("Black", 3999.99, "48小时");
Console.WriteLine($"颜色:{myPhone.Color}, 续航:{myPhone.MaxBatteryLife}");
- 关键点:
- 子类通过
: base(...)
传递参数至父类构造器 - 子类直接使用父类属性
Color
/Price
(5)常见错误及规避
class Child : Parent {public Child() { }
}
- 混淆重写与隐藏:多态场景下,
new
隐藏方法不会覆盖父类引用调用结果
Parent obj = new Child();
obj.Method();
(6)总结建议
- 优先组合而非继承:过度继承易导致层级复杂,推荐用组合(类成员引用其他类)替代。
- 合理使用
sealed
:对无需再派生的类标记 sealed
阻止继承,提升安全性。 - 实战练习:对比
struct
(值类型无继承)与 class
的适用场景。
4、接口
(1)本质与核心特性
- 契约式设计
- 接口定义类或结构体必须实现的方法、属性、事件或索引器签名,但不含具体实现。
- 示例:
public interface ILogger {void Log(string message); string LogLevel { get; set; }
}
- 关键特性
- 多重继承:一个类可实现多个接口,突破 C# 单继承限制。
- 解耦与标准化:强迫实现类遵循统一规范,提升代码可维护性。
- 默认公开性:接口成员隐式为
public
,不可添加访问修饰符。
(2)核心应用场景
IAnimal animal = new Dog();
animal.Speak();
animal = new Cat();
animal.Speak();
public interface ISortStrategy { void Sort(List<int> data); }
public class QuickSort : ISortStrategy { }
public class Context { private ISortStrategy _strategy;public void SetStrategy(ISortStrategy strategy) => _strategy = strategy;
}
- 工厂模式:接口定义创建契约,子类决定实例化逻辑。
- 组件化开发:定义数据访问层接口
IDataAccess
,支持切换不同数据库实现:
public class MongoDBService : IDataAccess { }
public class SqlService : IDataAccess { }
- 跨模块通信:事件驱动架构中,通过接口标准化事件处理:
public interface IEventHandler {void Handle(Event e);
}
(3)高级特性与注意事项
- 显式接口实现:解决同名方法冲突,通过接口名限定调用:
void IInterfaceA.Method() { }
public interface IComboBox : ITextBox, IListBox { }
public interface IRepository<T> {T GetById(int id);
}
- 使用限制
- 不可包含字段/构造函数:仅定义行为契约。
- 默认方法(C# 8.0+):允许接口提供默认实现,减少破坏性升级。
(4)最佳实践总结
- 命名规范:接口名以
I
开头(如 IEnumerable
)。 - 单一职责:每个接口聚焦单一功能(如
ISaveable
而非 IFileOperations
)。 - 接口隔离:避免“胖接口”,按需拆分为小接口。
- 依赖倒置:高层模块依赖抽象接口,而非具体实现。
5、抽象类
(1)基础概念
public abstract class Animal
{public abstract void MakeSound(); public void Sleep() {Console.WriteLine("Sleeping...");}
}
- 核心特性: * ❗ 不可实例化:不能通过 `new` 创建对象(如 `new Animal()` 非法)。 * ✅ 可包含混合成员:可同时有抽象方法(无方法体)和非抽象方法(有实现)。 * 🔒 强制子类实现:非抽象子类必须重写所有抽象方法(否则编译错误)。
- 设计目的
- 为相关类提供共性模板(如动物都有
MakeSound
行为),具体实现由子类完成。 - 解决类族中 “部分行为需自定义,部分行为可复用” 的问题。
️(2)核心规则
- 抽象方法约束
- 抽象方法只能存在于抽象类中,且无方法体(以分号结尾):
public abstract void Eat();
- 子类必须通过 `override` 实现:
public class Dog : Animal
{public override void MakeSound() => Console.WriteLine("Woof!");
}
public abstract class Fruit
{public string Color { get; set; } public abstract float Price { get; }
}
- 构造与继承机制
- 抽象类可以有构造函数(通常用于初始化公共字段)。
- 支持多层继承(如
Animal → Mammal → Dog
)。
(3)抽象类 vs 接口
特性 | 抽象类 (abstract class ) | 接口 (interface ) |
---|
实现继承 | ✅ 单继承 | ✅ 多实现 |
方法实现 | ✅ 可含具体方法 | ❌ 仅方法签名 |
字段/属性 | ✅ 可包含字段和属性初始值 | ❌ 仅自动属性(无字段) |
设计目标 | 表征 IS-A 关系(如狗是动物) | 表征 CAN-DO 能力(如可飞行) |
- 决策建议:
- 需多态且无关类共享行为 → 接口(如日志服务
ILogger
)。 - 存在通用代码需复用 → 抽象类(如游戏实体
GameEntity
基类)。
(4)典型应用场景
public abstract class Character {public int Health { get; set; }public abstract void Attack(); public void TakeDamage(int damage) => Health -= damage;
}
public class Warrior : Character {public override void Attack() => Console.WriteLine("Sword slash!");
}
public abstract class Logger {public abstract void Log(string message); public void LogError(string error) => Log($"[ERROR] {error}");
}
️(5)关键注意事项
- 禁止密封:抽象类不可用
sealed
修饰(否则无法继承)。 - 构造函数用途:仅用于子类初始化(如
public Dog() : base("Mammal")
)。 - 多态性支持:可通过基类引用调用子类实现(如
Animal a = new Dog(); a.MakeSound();
)。
6、方法重写
(1)基础概念
- 定义与目的
- 重写(Override):派生类修改基类中声明为
virtual
或abstract
的方法实现,实现多态性。 - 核心价值:
- 扩展基类功能,支持差异化行为(如不同动物的叫声)。
- 符合开闭原则(对扩展开放,对修改关闭)。
- 语法规则
角色 | 关键字 | 示例 |
---|
基类方法 | virtual | public virtual void Speak() { ... } |
派生类方法 | override | public override void Speak() { ... } |
- 关键限制:
- 静态方法、私有方法(
private
)不可重写; - 重写方法必须与基类方法完全一致(名称、参数、返回值)。
(2)核心机制
- 多态性实现:通过基类引用调用方法,运行时自动绑定派生类实现(动态绑定)。
Animal dog = new Dog();
dog.Speak();
- 调用基类实现:使用
base
关键字访问被重写的基类方法:
public class Car : Vehicle { public override void Start() { base.Start(); Console.WriteLine("引擎点火"); }
}
- 抽象方法重写:抽象类中声明
abstract
方法,强制派生类重写:
public abstract class Shape { public abstract void Draw();
}
public class Circle : Shape { public override void Draw() => Console.WriteLine("绘制圆形");
}
(3)重写 vs 隐藏
特性 | 重写(override) | 隐藏(new) |
---|
基类要求 | virtual /abstract 方法 | 任意方法(无需virtual ) |
多态行为 | 运行时按实际类型调用派生类方法 | 编译时按声明类型调用 |
调用结果 | Animal obj = new Dog(); obj.Speak() → 调用Dog.Speak() | Animal obj = new Dog(); obj.Speak() → 调用Animal.Speak() |
- 优先使用重写:确保多态性,避免隐藏导致的逻辑混淆。
️(4)实战应用场景
public class Cat : Animal { public override void Speak() => Console.WriteLine("猫叫:喵喵!");
}
public class ElectricCar : Car { public override void Start() { base.Start(); Console.WriteLine("电池系统激活"); }
}
public abstract class GameAI { public void Turn() { CollectResources(); BuildStructures(); } protected virtual void BuildStructures() { }
}
public class OrcAI : GameAI { protected override void BuildStructures() => Console.WriteLine("建造兽人兵营");
}
(5)关键注意事项
- 访问权限:重写方法访问级别需与基类一致(如
public
重写public
)。 - 异常处理:重写方法抛出的异常类型必须与基类相同或是其子类。
- 性能优化:避免深度继承链中频繁重写,可能导致调用栈过深。
- 调试技巧:使用
base
调试基类逻辑,用this
验证派生类行为,结合断点观察多态调用链。
7、命名空间
(1)核心作用
- 解决命名冲突
- 将全局作用域划分为独立区块,避免不同库中同名类型/函数冲突。
- 使用时通过完整限定名区分:
CompanyA.Project.Logger
vs CompanyB.Project.Logger
。
namespace CompanyA.Project { class Logger { } }
namespace CompanyB.Project { class Logger { } }
- 代码组织与封装
- 类似文件系统的文件夹结构,将相关类型分组管理(如
System.IO
处理输入输出)。 - 支持嵌套命名空间(如
System.Collections.Generic
),实现逻辑分层。
(2)语法与使用方式
namespace MyApplication.Data {class Database { }
}
- 引用命名空间
- 完全限定名:直接使用完整路径(
System.Console.WriteLine()
)。 using
指令:简化代码,避免重复书写命名空间:
using System;
Console.WriteLine("Hello");
- 别名(Alias):解决同名冲突或简化长命名空间:
using ProjectA = CompanyA.Project;
ProjectA.Logger.Log();
(3)实际应用场景
- 类库开发
- 第三方库(如NuGet包)必须使用独立命名空间,防止污染用户全局作用域。
- 示例:ST硬件驱动库通过命名空间封装外设寄存器组,提高可读性。
- 大型项目分层
namespace App.BusinessLogic;
namespace App.DataAccess;
namespace App.WebUI;
- 分层后,各模块通过`using`按需引用,减少耦合。
- 与物理文件的关系
- 命名空间可跨多个文件定义(非连续),例如:
File1.cs
: namespace MyLib { class A { } }
File2.cs
: namespace MyLib { class B { } }
编译器自动合并为同一命名空间。
(4)最佳实践与注意事项
- 避免全局污染
- 慎用
using namespace *;
(如using namespace System
),尤其在头文件中,易引发冲突。 - 优先使用局部
using
或完全限定名。
- 命名规范
- 采用公司/项目名作为根命名空间(如
Microsoft.Azure
)。 - 层次清晰:
<Company>.<Product>.<Module>
。
- 未命名命名空间:用于文件内私有类型(等效于
internal
访问修饰符),限制外部访问:
namespace { class InternalHelper { }
}