一、:继承(Inheritance)
1. 继承概念
继承(Inheritance)是一种机制,它允许一个类(子类/派生类)从另一个类(父类/基类)中继承属性和方法。 这种关系使得子类可以重用父类的代码,同时可以在子类中添加或修改属性和方法。 继承有助于减少代码重复和提高代码的可维护性。
2. C#中的继承语法
在C#中,实现继承非常简单,使用冒号:即可。
"is-a"关系:继承描述的是一种"is-a"(是一个)的关系。
第一步:创建基类 Enemy
csharp
// 基类 / 父类
public class Enemy
{
public int health;
public int attackPower;
public void Move()
{
Console.WriteLine("敌人正在移动。");
}
public void Attack()
{
Console.WriteLine("敌人发起了攻击!");
}
}
第二步:创建派生类 Goblin 和 Skeleton
csharp
// 派生类 / 子类
public class Goblin : Enemy // Goblin 继承自 Enemy
{
// 它自动拥有了 health, attackPower, Move(), Attack()
public void StealGold()
{
Console.WriteLine("哥布林偷走了你的金币!");
}
}
// 派生类 / 子类
public class Skeleton : Enemy // Skeleton 继承自 Enemy
{
// 它也自动拥有了 health, attackPower, Move(), Attack()
public void Reassemble()
{
Console.WriteLine("骷髅兵正在重组身体!");
}
}
现在,代码变得干净、有条理了。我们可以这样使用它们:
csharp
Goblin goblin = new Goblin();
goblin.health = 100; // 继承自 Enemy
goblin.Move(); // 继承自 Enemy
goblin.StealGold(); // 自己的特有方法
Skeleton skeleton = new Skeleton();
skeleton.attackPower = 20; // 继承自 Enemy
skeleton.Attack(); // 继承自 Enemy
skeleton.Reassemble(); // 自己的特有方法
继承的好处:
- 代码重用:公共代码只需写一次。
- 逻辑清晰:建立了清晰的类层次结构。
- 易于维护:修改基类,所有子类都会自动更新。
3. 访问修饰符与继承
public:公开的。子类可以访问,外部代码也可以访问。private:私有的。只有在当前类内部可以访问,子类无法访问。protected:受保护的。介于public和private之间。它允许在基类类和其所有子类中访问,但对外部代码是私有的。
让我们改进Enemy类:
csharp
public class Enemy
{
protected int health; // 改为 protected,子类可以直接访问,但外部不行
private string internalId = "ENEMY_001"; // private,子类无法访问
public int AttackPower { get; set; } // public 属性
// ...
}
public class Goblin : Enemy
{
public void TakeDamage(int damage)
{
this.health -= damage; // 正确!可以访问 protected 成员
// this.internalId = "GOBLIN_ID"; // 错误!无法访问 private 成员
}
}
4. 构造函数与 base 关键字
一个重要规则:子类不会继承父类的构造函数。
当创建一个子类实例时,系统会先调用父类的构造函数,然后再调用子类的构造函数。这是为了确保在子类初始化之前,父类的部分已经被正确地创建了。
如果父类有一个带参数的构造函数,子类必须通过: base()语法显式地调用它。
csharp
public class Enemy
{
public int health;
// 父类的构造函数
public Enemy(int initialHealth)
{
this.health = initialHealth;
Console.WriteLine("Enemy 构造函数被调用。");
}
}
public class Goblin : Enemy
{
public string weapon;
// 子类的构造函数,通过 base 调用父类构造函数
public Goblin(int initialHealth, string weapon) : base(initialHealth)
{
// 先调用 base(initialHealth),即父类的构造函数
// 然后再执行这里的代码
this.weapon = weapon;
Console.WriteLine("Goblin 构造函数被调用。");
}
}
// 使用
Goblin goblin = new Goblin(100, "生锈的匕首");
// 输出:
// Enemy 构造函数被调用。
// Goblin 构造函数被调用。
二、多态
1. 多态的概念
它允许不同类的对象对同一消息(方法调用)作出不同的响应
在C#中,多态主要通过虚方法( virtual )和方法重写( override )来实现,同时接口( interface )和抽象类( abstract class )也对多态提供了支持
2. 多态的实现
要在C#中实现多态,需要三个关键要素:
virtual(虚拟) :在父类 的方法前加上virtual关键字,表示这个方法是"虚拟的",允许被子类重写(Override)。override(重写) :在子类 中,使用override关键字来提供一个父类virtual方法的新实现。- 基类引用指向子类对象 :
Enemy enemy = new Goblin();
让我们来升级我们的敌人系统,实现多态的Attack方法。
第一步:在 Enemy 中将 Attack 声明为 virtual
csharp
public class Enemy
{
// ... 其他成员 ...
// virtual 关键字表示这个方法可以被子类重写
public virtual void Attack()
{
Console.WriteLine("敌人发起了基础攻击!");
}
}
第二步:在 Goblin 和 Skeleton 中 override 这个方法
csharp
public class Goblin : Enemy
{
// ...
// override 关键字表示我们正在重写基类的同名方法
public override void Attack()
{
// base.Attack(); // 可以选择性地调用基类的方法
Console.WriteLine("哥布林用匕首进行了一次快速突刺!");
}
}
public class Skeleton : Enemy
{
// ...
public override void Attack()
{
Console.WriteLine("骷髅兵挥舞着骨头棒砸向你!");
}
}
第三步:见证多态的使用! 我们可以创建一个Enemy类型的列表,但里面可以装入各种不同类型的敌人!
csharp
// 创建一个敌人列表
List<Enemy> enemies = new List<Enemy>();
// 添加不同类型的敌人,注意,都是用子类实例化的
enemies.Add(new Goblin());
enemies.Add(new Skeleton());
enemies.Add(new Enemy()); // 也可以添加一个普通的 Enemy
// 遍历敌人列表,让他们一起攻击
Console.WriteLine("====== 战斗开始 ======");
foreach (Enemy currentEnemy in enemies)
{
// 最关键的一行代码!
//实现多态,不同的对象对同一个方法做出不一样的响应。
currentEnemy.Attack();
}
输出结果:
diff
====== 战斗开始 ======
哥布林用匕首进行了一次快速突刺!
骷髅兵挥舞着骨头棒砸向你!
敌人发起了基础攻击!
三、深入探索 ------ abstract, sealed 和 new
1. abstract 类与方法
- 抽象类 (
abstract class) :不能被实例化的类。new Enemy()会报错。它只能被用作基类。 - 抽象方法 (
abstract method) :在抽象类中,只有声明没有方法体的方法。它强制 所有非抽象的子类必须使用override来实现这个方法。
csharp
// 抽象基类
public abstract class Enemy
{
public int health;
// 抽象方法,没有实现,用分号结尾
// 它强制所有子类必须提供自己的攻击方式
public abstract void Attack();
// 抽象类中也可以有普通方法
public void Move()
{
Console.WriteLine("敌人正在移动。");
}
}
public class Goblin : Enemy
{
// 必须重写父类的抽象方法 Attack
public override void Attack()
{
Console.WriteLine("哥布林用匕首进行了一次快速突刺!");
}
}
abstract vs virtual 的区别:
virtual方法有 实现体,子类可以重写或者不重写都行。abstract方法没有 实现体,子类必须 重写它(除非子类自己也是abstract的)。abstract方法只能存在于abstract类中。
2. sealed 类与方法继承的终点
与abstract相反,sealed关键字用来阻止继承。
- 密封类 (
sealed class):不能被任何其他类继承。 - 密封方法 (
sealed override) :在一个子类中,可以把一个重写的方法标记为sealed,这样它的下一级子类就不能再继续重写这个方法了。
csharp
// 密封类,不能被继承
public sealed class SuperBoss : Enemy
{
public override void Attack()
{
Console.WriteLine("超级Boss发动了灭世攻击!");
}
}
// public class MiniBoss : SuperBoss { } // 错误!无法从密封类型"SuperBoss"派生
public class SkeletonWarrior : Skeleton
{
// 将 Attack 方法密封,SkeletonWarrior 的子类将无法再重写它
public sealed override void Attack()
{
Console.WriteLine("骷髅战士发动了精准的剑击!");
}
}
3. new 关键字
当子类中一个方法的签名(名称和参数)与父类完全相同时,你可以用new关键字来"隐藏"父类的方法。
这不是多态!
csharp
public class Parent
{
public void Greet()
{
Console.WriteLine("Hello from Parent!");
}
}
public class Child : Parent
{
// 使用 new 关键字隐藏了父类的 Greet 方法
public new void Greet()
{
Console.WriteLine("Hello from Child!");
}
}
// 观察区别
Parent p1 = new Parent();
p1.Greet(); // 输出: Hello from Parent!
Child c1 = new Child();
c1.Greet(); // 输出: Hello from Child!
// 关键区别在这里!
Parent p2 = new Child(); // 父类引用指向子类对象
p2.Greet(); // 输出: Hello from Parent!
总结:override vs new
override是真正的多态。运行时决定调用哪个版本。new是方法隐藏。编译时根据引用的类型决定调用哪个版本。- 绝大多数情况下,你想要的是
override而不是new。
结语
点个赞,关注我获取更多实用 C# 技术干货!如果觉得有用,记得收藏本文