继承和多态
面向对象方法中的继承体现了现实世界中的“一般特殊关系”。基类代表一般性事物,而派生类是一种特殊的基类,是对基类的补充和细化。不同的派生类执行同一个方法时,会出现不同的行为,这就是多态。
实现继承
C#中用如下语法实现继承:
class 派生类:基类 {类的成员}
eg:public class MyButton:System.Windows.Forms.Button {}
C#中所有的类都是直接或间接从System.Object类派生来的,如果定义一个类时没有指明基类,那么这个类的基类就是System.Object。.NET Framework中所有的类都是直接或间接派生自System.Object,甚至包括像int、string等简单的类型也是。
因此C#中所有的类都是直接或间接继承自System.Object类,从而也都拥有System.Object类中所定义的公共成员。
C#只允许一个类仅从一个类继承,但是一个类可以同时从多个接口继承。
变量的定义类型和实际类型:
定义变量时的类型叫定义类型。变量被赋予值时的类型叫实际类型。变量的定义类型与实际类型不一定相同。如下
1 object obj1, obj2; 2 3 obj1 = 123; 4 obj2 = "Hello";
obj1和obj2定义类型都为object,但obj1实际类型是int,obj2实际类型是string。变量的类型都可以通过System.Object的GetType方法获得,GetType返回一个System.Type类型的对象,用于描述变量的类型信息。由于变量可以多次被赋值,所以变量的
实际类型在程序运行过程中是可以动态改变的。如下:
1 static void Main(string[] args) 2 { 3 object obj1, obj2, obj3; 4 5 Console.WriteLine("定义三个object类型变量"); 6 obj1 = 123; 7 Console.WriteLine("将obj1赋值为123"); 8 obj2 = "Hello"; 9 Console.WriteLine("将obj2赋值为"Hello""); 10 obj3 = DateTime.Now; 11 Console.WriteLine("将obj3赋值为当前时间"); 12 Console.WriteLine("obj1的实际类型为: " + obj1.GetType().ToString()); 13 Console.WriteLine("obj2的实际类型为: " + obj2.GetType().ToString()); 14 Console.WriteLine("obj3的实际类型为: " + obj3.GetType().ToString()); 15 16 obj3 = new int[] { 1, 2, 3 }; 17 Console.WriteLine("将obj3赋值为一个整形数组"); 18 Console.WriteLine("obj3的实际类型为: " + obj3.GetType().ToString()); 19 20 Console.ReadKey(); 21 }
运行结果为:
定义三个object类型变量 将obj1赋值为123 将obj2赋值为"Hello" 将obj3赋值为当前时间 obj1的实际类型为: System.Int32 obj2的实际类型为: System.String obj3的实际类型为: System.DateTime 将obj3赋值为一个整形数组 obj3的实际类型为: System.Int32[]
从运行结果来看,3个变量的定义类型都为object,实际类型分别为Int32、String和DateTime,而且obj3实际类型发生了变化,从DateTime变为int[]。
变量只能按照定义的类型来使用。上面例子中obj3定义类型为object,就只能当object类型来使用,虽然后面实际类型为int[],如果把obj3当int[]来使用那么会报错,如下:
1 obj3[0] = 1;//报错
基类和派生类之间的类型转换
派生类向基类的转换是安全的,总可以成功;但是基类向派生类转换时,只有当变量的实际类型是目标类型或或目标类型的派生类时,转换才能成功,否则会抛出System.InvalidCastException异常。
虚方法和多态
如果基类和派生类都定义了相同的签名的方法,那么程序在运行时会调用那个方法呢?如下:
1 class Mammal 2 { 3 public void bark() 4 { 5 Console.WriteLine("Mammal.bark() 哺乳动物叫声各不相同"); 6 } 7 } 8 9 class Dog:Mammal 10 { 11 public void bark() 12 { 13 Console.WriteLine("Dog.bark() 狗的叫声汪汪汪"); 14 } 15 } 16 17 static void Main(string[] args) 18 { 19 Mammal m = new Mammal(); 20 Dog d = new Dog(); 21 22 Console.WriteLine("Main 调用 Mammal.bark()"); 23 m.bark(); 24 25 Console.WriteLine("Main 调用 Dog.bark()"); 26 d.bark(); 27 28 Console.ReadLine(); 29 }
运行结果
Main 调用 Mammal.bark()
Mammal.bark() 哺乳动物叫声各不相同
Main 调用 Dog.bark()
Dog.bark() 狗的叫声汪汪汪
由结果可知调用Mammal类型变量的bark方法时Mammal类的bark方法被执行,调用Dog类的对象的bark方法时,Dog类的bark方法被执行。
如果定义类型与实际类型不一致时,会怎么样呢?
1 Mammal m; 2 Dog d = new Dog(); 3 4 m = d; 5 m.bark(); 6 d.bark();
运行结果
Mammal.bark() 哺乳动物叫声各不相同
Dog.bark() 狗的叫声汪汪汪
从运行结果可以看出,虽然m和d是同一个对象,但由于定义对象不同,掉用bark执行的代码也不一样。bark方法实际执行的代码是由定义类型决定的。所以m.bark()调用Mammal的bark方法,d.bark()调用Dog的bark方法。
在很多时候,开发人员并不希望程序这样运行,而是希望程序能够根据变量的实际类型来调用相应的方法。这样对于同一个Mammal类型的变量m,当其实际类型为不同的派生类时,调用m.bark()方法会产生不同的行为,这就是多态。
当基类和派生类都定义了相同签名的方法时,C#允许开发人员明确指定哪个方法应该被调用。是根据定义类型调用方法还是根据实际类型调用方法,C#通过虚方法、方法重写和方法隐藏实现这个功能。
如果想让程序在运行时根据变量的定义类型来决定调用那个方法,可以通过方法隐藏来实现;如果想让程序实现多态性,即在运行时根据变量的实际类型调用相应的方法,那么可以通过虚方法和方法重写实现。
定义方法时使用new关键字可以隐藏基类具有相同签名的方法,语法如下:
访问修饰符 new 返回值类型 方法名(参数列表){方法体}
上述代码预定一个普通方法的唯一区别是多了一个new关键字,new关键字表明这个方法将隐藏基类中相同签名的方法。new关键字可以放在访问修饰符的前面或后面都可以。
使用virtual关键字可以定义一个虚方法,虚方法可以在派生类中被重写。定义虚方法语法如下:
访问修饰符 virtual 返回值类型 方法名(参数列表) {方法体}
派生类使用override关键字重写基类中的虚方法,语法如下:
访问修饰符 override 返回值类型 方法名(参数列表) {方法体}
在基类中使用virtual关键字定义虚方法,在派生类中使用override关键字重写虚方法,可以使程序呈现多态性。
1 class Mammal 2 { 3 public virtual void bark() 4 { 5 Console.WriteLine("Mammal.bark() 哺乳动物叫声各不相同"); 6 } 7 8 public void walk() 9 { 10 Console.WriteLine("Mammal.walk() 哺乳动物行走"); 11 } 12 } 13 14 class Dog:Mammal 15 { 16 public override void bark() 17 { 18 Console.WriteLine("Dog.bark() 狗的叫声汪汪汪"); 19 } 20 21 public new void walk() 22 { 23 Console.WriteLine("Dog.walk() 狗奔跑很快"); 24 } 25 } 26 class Cat:Mammal 27 { 28 public override void bark() 29 { 30 Console.WriteLine("Cat.bark() 猫的叫声喵喵喵"); 31 } 32 33 public new void walk() 34 { 35 Console.WriteLine("Cat.walk() 猫行动敏捷"); 36 } 37 } 38 static void Main(string[] args) 39 { 40 Mammal m; 41 Cat c = new Cat(); 42 Dog d = new Dog(); 43 44 Console.WriteLine("调用bark方法"); 45 m = c; 46 m.bark(); 47 c.bark(); 48 49 m = d; 50 m.bark(); 51 d.bark(); 52 53 Console.WriteLine("调用walk方法"); 54 m = c; 55 m.walk(); 56 c.walk(); 57 58 m = d; 59 m.walk(); 60 d.walk(); 61 62 63 Console.ReadLine(); 64 }
运行结果
调用bark方法
Cat.bark() 猫的叫声喵喵喵
Cat.bark() 猫的叫声喵喵喵
Dog.bark() 狗的叫声汪汪汪
Dog.bark() 狗的叫声汪汪汪
调用walk方法
Mammal.walk() 哺乳动物行走
Cat.walk() 猫行动敏捷
Mammal.walk() 哺乳动物行走
Dog.walk() 狗奔跑很快()
由运行结果可以看出用new关键字进行方法隐藏后,被调用的方法由变量的定义类型决定。虽然m实际是Dog类(或Cat类)的实例,但是由于m被定义成一个Mammal类型的变量,所以当调用m.walk()方法时,总是调用Mammal类的walk方法,
而不会调用Cat或Dog类的walk方法。
对于使用virtual和override关键字声明的方法,再调用时由变量的实际类型决定调用那个类的相应方法。m先后被赋予Cat类型和Dog类型的值,在调用bark方法时,先后调用了Cat类的bark方法和Dog类的bark方法。同一段代码m.bark,由于变量
的值不同而表现出不同的行为,形成了多态性。