1. 入门
今天这个主题可能有点抽象,但相信我,没它你后面啥也写不了。我们要聊聊编程里的继承。今天只是浅尝一下,后面还会详细讲。
想象一下,你要写一个动物园动物管理的程序。你有各种动物:狮子、老虎、大象、鹦鹉。它们都是动物,但又各有特色:
- 狮子有鬃毛。
- 老虎有条纹。
- 大象很大,还有长鼻子。
- 鹦鹉会说话。
如果我们每种动物都单独写一遍,代码会非常像:
// 狮子
public class Lion
{
public string Name { get; set; }
public int Age { get; set; }
public string Species { get; set; } = "狮子"; // 永远是"狮子"
public void Eat() { Console.WriteLine("狮子吃肉。"); }
public void Sleep() { Console.WriteLine("狮子在睡觉。"); }
public void Roar() { Console.WriteLine("狮子在咆哮!"); }
public string ManeColor { get; set; } // 狮子的特点 - 鬃毛
}
// 老虎
public class Tiger
{
public string Name { get; set; }
public int Age { get; set; }
public string Species { get; set; } = "老虎"; // 永远是"老虎"
public void Eat() { Console.WriteLine("老虎吃肉。"); }
public void Sleep() { Console.WriteLine("老虎在睡觉。"); }
public void Stride() { Console.WriteLine("老虎在悄悄走。"); }
public string StripePattern { get; set; } // 老虎的特点 - 条纹
}
看到没,代码重复好多?Name、Age、Eat()、Sleep()——这些所有动物都一样!要是有100种动物呢?简直灾难!
这时候,继承就能帮大忙了。
2. C#里的类继承是什么?
继承是面向对象编程(OOP)最基础的原则之一。它让你可以基于已有的类创建新类。新类(子类、派生类)会获得(继承)父类(基类、父类)的所有属性(字段)和行为(方法)。
就像现实生活中:你会继承父母的一些特征,但你也有自己的独特之处。
在C#里,继承用冒号:写在子类名后面:
// 父类(基类)- Animal
public class Animal
{
public string Name { get; set; } // 动物名字
public int Age { get; set; } // 动物年龄
public void Eat()
{
Console.WriteLine($"{Name}在吃东西。");
}
public void Sleep()
{
Console.WriteLine($"{Name}在睡觉。");
}
}
// 子类 - Lion 继承自 Animal
public class Lion : Animal // <-- 这就是继承!
{
public string ManeColor { get; set; } // 狮子的独特属性
public void Roar() // 狮子的独特行为
{
Console.WriteLine($"{Name}在咆哮: RRRRRR!");
}
}
// 另一个子类 - Tiger 继承自 Animal
public class Tiger : Animal
{
public string StripePattern { get; set; } // 老虎的独特属性
public void Stride() // 老虎的独特行为
{
Console.WriteLine($"{Name}悄悄地走着。");
}
}
现在,如果我们创建Lion或Tiger对象,它们自动就有Name和Age属性,还有Eat()和Sleep()方法,因为这些都是从Animal继承来的。
class Program
{
static void Main(string[] args)
{
Lion simba = new Lion();
simba.Name = "辛巴";
simba.Age = 5;
simba.ManeColor = "金色";
simba.Eat(); // 从Animal继承的方法
simba.Sleep(); // 从Animal继承的方法
simba.Roar(); // Lion自己的方法
Console.WriteLine($"{simba.Name} - 年龄: {simba.Age}, 鬃毛颜色: {simba.ManeColor}");
Tiger shereKhan = new Tiger();
shereKhan.Name = "谢尔汗";
shereKhan.Age = 7;
shereKhan.StripePattern = "经典";
shereKhan.Eat(); // 从Animal继承的方法
shereKhan.Sleep(); // 从Animal继承的方法
shereKhan.Stride(); // Tiger自己的方法
Console.WriteLine($"{shereKhan.Name} - 年龄: {shereKhan.Age}, 条纹: {shereKhan.StripePattern}");
}
}
继承的核心思想:
- 代码复用(Code Reusability): 把通用逻辑写在基类里,避免重复代码。
- 层级结构(Hierarchy): 形成“通用-特殊”的逻辑结构(Animal → Lion/Tiger)。
- 多态(Polymorphism):(先提一下,后面详细讲)你可以用基类引用来操作子类对象,比如有个Animal列表,里面放狮子、老虎,然后都能调用Eat()方法。
3. 调用基类构造函数(base)
当你创建子类对象(比如new Lion()),C#会自动在调用子类构造函数之前先调用基类(这里是Animal)的构造函数。这样能保证基类部分被正确初始化。
有时候你需要把参数传给基类构造函数,这时就在子类构造函数签名后面用base关键字。
public class Animal
{
public string Name { get; set; }
public int Age { get; set; }
// 基类构造函数
public Animal(string name, int age)
{
Name = name;
Age = age;
Console.WriteLine($"Animal构造函数: 创建了动物{Name}, 年龄{Age}。");
}
public void Eat()
{
Console.WriteLine($"{Name}在吃东西。");
}
public void Sleep()
{
Console.WriteLine($"{Name}在睡觉。");
}
}
public class Lion : Animal
{
public string ManeColor { get; set; }
// Lion构造函数,调用基类Animal的构造函数
public Lion(string name, int age, string maneColor) : base(name, age) // <-- 这就是'base'!
{
ManeColor = maneColor;
Console.WriteLine($"Lion构造函数: 鬃毛颜色{ManeColor}。");
}
public void Roar()
{
Console.WriteLine($"{Name}在咆哮: RRRRRR!");
}
}
class Program
{
static void Main(string[] args)
{
// 创建Lion时,先调用Animal构造函数,再调用Lion的
Lion simba = new Lion("辛巴", 5, "金色");
simba.Roar();
}
}
执行new Lion("辛巴", 5, "金色")时会发生什么:
- 会调用Lion(string name, int age, string maneColor)构造函数。
- 因为有: base(name, age),会先进入Animal(string name, int age)构造函数。
- 执行Animal构造函数代码,你会看到Animal构造函数: 创建了动物辛巴, 年龄5。
- 然后回到Lion构造函数。
- 执行Lion构造函数代码,你会看到Lion构造函数: 鬃毛颜色金色。
这点很重要:基类总是先初始化!
4. is和as操作符
在C#编程世界里,你经常会遇到需要判断对象类型或者尝试把它转换成别的类型的情况。这时候is和as操作符就很有用了。它们在继承和多态(后面会讲)里特别常见,不过现在就能先学个基础。
is操作符(类型检查)
is操作符可以判断一个对象是不是某种类型。如果能转换成指定类型,返回true,否则返回false。
语法: 表达式 is 类型
static void AnalyzeObject(object obj)
{
if (obj is string)
{
Console.WriteLine("这是字符串!");
}
else if (obj is int)
{
Console.WriteLine("这是整数!");
}
else
{
Console.WriteLine("这是别的东西。");
}
}
static void Main()
{
AnalyzeObject("你好,世界!"); // 这是字符串!
AnalyzeObject(123); // 这是整数!
AnalyzeObject(3.14); // 这是别的东西。
AnalyzeObject(new int[] { 1, 2 }); // 这是别的东西。
}
is配合模式匹配:
现在的C#,is不仅能判断类型,还能直接声明一个变量接收转换后的值,这叫“模式匹配”,代码更简洁。
static void AnalyzeObjectWithPatternMatching(object obj)
{
if (obj is string message) // 如果obj是字符串,赋值给message
{
Console.WriteLine($"这是字符串,长度: {message.Length}");
}
else if (obj is int number) // 如果obj是int,赋值给number
{
Console.WriteLine($"这是数字,乘2后: {number * 2}");
}
else
{
Console.WriteLine("类型没认出来。");
}
}
static void Main()
{
AnalyzeObjectWithPatternMatching("示例"); // 这是字符串,长度: 2
AnalyzeObjectWithPatternMatching(50); // 这是数字,乘2后: 100
AnalyzeObjectWithPatternMatching(new object()); // 类型没认出来。
}
这种is+模式匹配的方式比传统的强制类型转换再判null更安全、更好读。
as操作符(安全类型转换)
as操作符会尝试把对象转换成指定类型。如果能转换,就返回转换后的对象;如果不能(类型不兼容),as会返回null,不会抛异常。所以说as是“安全”的类型转换。
语法: 表达式 as 类型
static void ProcessData(object data)
{
string text = data as string; // 尝试把data转成string
if (text != null) // 判断转换是否成功
{
Console.WriteLine($"处理字符串: {text.ToUpper()}");
}
else
{
Console.WriteLine("数据不是字符串。");
}
}
static void Main()
{
ProcessData("hello"); // 处理字符串: HELLO
ProcessData(123); // 数据不是字符串。
ProcessData(null); // 数据不是字符串。
}
什么时候用as而不是直接强制转换(类型)表达式:
- 安全: 如果你不确定对象是不是你想要的类型,as能避免InvalidCastException,直接返回null,你可以自己处理。
- 只能用于引用类型: as只能用在引用类型(类、接口、委托、数组)之间,不能把值类型(比如int转double)用as转换。
注意: 如果你确定对象一定是目标类型,或者你要转换值类型,就用直接强制转换(类型)表达式。如果类型不对,强制转换会抛异常(InvalidCastException),有时候这正好能帮你发现程序逻辑错误。
GO TO FULL VERSION