1. 继承机制深度解析
在C#面向对象编程中,继承是代码复用的核心机制。通过继承,子类可以自动获得父类的所有非私有成员(字段、属性、方法等),同时还能添加自己特有的成员或修改继承来的行为。
1.1 基础继承语法
典型的继承声明如下:
csharp复制public class ParentClass
{
// 父类成员
}
public class ChildClass : ParentClass
{
// 子类特有成员
}
这种单继承结构(C#不支持多继承)形成了清晰的类层次关系。在实际项目中,我们常用继承来表达"is-a"关系,比如"储蓄账户是一种银行账户"。
1.2 访问修饰符的影响
继承时成员的可见性由访问修饰符决定:
public:完全公开protected:仅对子类可见private:仅当前类可见internal:同一程序集内可见
特别要注意的是,构造方法不会被继承。子类必须通过: base()语法显式调用父类构造方法:
csharp复制public class ChildClass : ParentClass
{
public ChildClass(int param) : base(param)
{
// 子类初始化
}
}
2. 方法重写技术详解
方法重写是面向对象多态性的关键实现方式,允许子类改变继承自父类的方法行为。
2.1 virtual与override关键字
标准重写模式需要三个要素:
- 父类方法声明为
virtual - 子类方法使用
override标记 - 方法签名完全一致
csharp复制public class Shape
{
public virtual void Draw()
{
Console.WriteLine("绘制基本图形");
}
}
public class Circle : Shape
{
public override void Draw()
{
Console.WriteLine("绘制圆形");
base.Draw(); // 可选调用父类实现
}
}
2.2 重写与隐藏的区别
使用new关键字实现方法隐藏(method hiding)与重写有本质区别:
csharp复制public class Base
{
public void Method() { Console.WriteLine("Base"); }
}
public class Derived : Base
{
public new void Method() { Console.WriteLine("Derived"); }
}
// 使用时:
Base obj = new Derived();
obj.Method(); // 输出"Base"(隐藏) vs "Derived"(重写)
3. 高级继承技巧
3.1 抽象类与抽象方法
当需要强制子类实现某些方法时,可以使用抽象类和抽象方法:
csharp复制public abstract class Animal
{
public abstract void MakeSound();
public void Sleep()
{
Console.WriteLine("Sleeping...");
}
}
public class Dog : Animal
{
public override void MakeSound()
{
Console.WriteLine("Bark!");
}
}
3.2 密封类与方法
使用sealed关键字可以阻止进一步继承:
csharp复制public sealed class FinalClass { } // 不能被继承
public class Parent
{
public virtual void Method() { }
}
public class Child : Parent
{
public sealed override void Method() { } // 子类不能再重写
}
4. 实战应用与最佳实践
4.1 银行账户案例实现
下面是一个完整的银行账户继承体系示例:
csharp复制public class BankAccount
{
protected decimal _balance;
public BankAccount(decimal initialBalance)
{
_balance = initialBalance;
}
public virtual void Withdraw(decimal amount)
{
if (amount > _balance)
throw new InvalidOperationException("余额不足");
_balance -= amount;
}
}
public class InterestAccount : BankAccount
{
public InterestAccount(decimal initialBalance) : base(initialBalance) { }
public override void Withdraw(decimal amount)
{
// 利息账户允许小额透支
if (amount > _balance + 1000)
throw new InvalidOperationException("超过透支限额");
_balance -= amount;
}
public void ApplyInterest(decimal rate)
{
_balance *= (1 + rate);
}
}
4.2 继承设计原则
- 里氏替换原则:子类应该能够完全替换父类而不影响程序正确性
- 组合优于继承:优先考虑使用组合而非深层次的继承
- 单一职责:每个类应该只有一个改变的理由
- 开闭原则:对扩展开放,对修改关闭
5. 常见问题与解决方案
5.1 基类构造方法调用问题
当忘记调用基类构造方法时:
csharp复制public class Child : Parent
{
public Child() // 错误:没有调用基类构造方法
{
}
}
解决方案是显式调用基类构造方法:
csharp复制public class Child : Parent
{
public Child() : base() // 正确
{
}
}
5.2 重写与隐藏混淆
常见错误是混淆override和new的行为差异。记住:
override修改虚方法调用行为new只是隐藏父类方法
5.3 多级继承的陷阱
避免过深的继承层次(通常不超过3层),过深的继承会导致:
- 代码难以理解
- 方法调用链复杂
- 脆弱基类问题
6. 性能考量与优化
6.1 虚方法调用开销
虚方法调用比非虚方法稍慢,因为需要运行时查找方法表。在性能关键路径上:
- 考虑使用
sealed限制继承 - 对于简单方法,使用非虚方法
6.2 对象内存布局
继承会影响对象内存布局:
- 子类对象包含父类所有字段
- 方法表指针在继承层次中共享
7. 现代C#中的继承演进
7.1 默认接口方法
C# 8.0引入了默认接口方法,提供了另一种代码复用方式:
csharp复制public interface ILogger
{
void Log(string message);
// 默认实现
void LogError(string error)
{
Log($"ERROR: {error}");
}
}
7.2 记录类型(record)的继承
记录类型支持继承但有一些限制:
csharp复制public record Person(string Name);
public record Employee(string Name, decimal Salary) : Person(Name);
8. 单元测试中的继承
测试继承层次时的策略:
- 为每个具体类编写测试
- 测试父类行为在子类中的保持
- 使用抽象测试类测试抽象类
csharp复制[TestClass]
public abstract class AccountTests
{
protected abstract BankAccount CreateAccount(decimal balance);
[TestMethod]
public void Withdraw_ReducesBalance()
{
var account = CreateAccount(100);
account.Withdraw(50);
Assert.AreEqual(50, account.Balance);
}
}
[TestClass]
public class InterestAccountTests : AccountTests
{
protected override BankAccount CreateAccount(decimal balance)
{
return new InterestAccount(balance);
}
[TestMethod]
public void ApplyInterest_IncreasesBalance()
{
var account = new InterestAccount(100);
account.ApplyInterest(0.1m);
Assert.AreEqual(110, account.Balance);
}
}
9. 设计模式中的继承应用
9.1 模板方法模式
利用继承实现算法框架:
csharp复制public abstract class DataProcessor
{
// 模板方法
public void Process()
{
OpenConnection();
ReadData();
TransformData();
CloseConnection();
}
protected abstract void ReadData();
protected abstract void TransformData();
private void OpenConnection() { /*...*/ }
private void CloseConnection() { /*...*/ }
}
9.2 装饰器模式
通过继承实现功能扩展:
csharp复制public abstract class Coffee
{
public abstract decimal Cost();
}
public class SimpleCoffee : Coffee
{
public override decimal Cost() => 5.0m;
}
public abstract class CoffeeDecorator : Coffee
{
protected Coffee _coffee;
public CoffeeDecorator(Coffee coffee)
{
_coffee = coffee;
}
}
public class MilkDecorator : CoffeeDecorator
{
public MilkDecorator(Coffee coffee) : base(coffee) { }
public override decimal Cost() => _coffee.Cost() + 2.0m;
}
10. 实际项目经验分享
在大型项目中应用继承时:
- 文档至关重要:清晰的类关系图能节省大量维护时间
- 避免过度设计:不是所有"is-a"关系都需要用继承实现
- 考虑未来扩展:为可能的重写点预留
virtual关键字 - 单元测试先行:确保子类行为符合预期
- 性能分析:在性能敏感场景测量虚方法调用开销
一个典型的错误案例是过早优化将方法声明为非虚的,导致后期无法扩展。建议初期将可能被重写的方法标记为virtual,除非有明确的性能需求。
