继承和多态
面向对象方法中的继承体现了现实世界中的“一般特殊关系”。基类代表一般性事物,而派生类是一种特殊的基类,是对基类的补充和细化。不同的派生类执行同一个方法时,会出现不同的行为,这就是多态。
实现继承
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.Int32obj2的实际类型为: System.Stringobj3的实际类型为: 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()\t 哺乳动物叫声各不相同"); 6 } 7 } 8 9 class Dog:Mammal10 {11 public void bark()12 {13 Console.WriteLine("Dog.bark()\t 狗的叫声汪汪汪");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()\t 哺乳动物叫声各不相同"); 6 } 7 8 public void walk() 9 {10 Console.WriteLine("Mammal.walk()\t 哺乳动物行走");11 }12 }13 14 class Dog:Mammal15 {16 public override void bark()17 {18 Console.WriteLine("Dog.bark()\t 狗的叫声汪汪汪");19 }20 21 public new void walk()22 {23 Console.WriteLine("Dog.walk()\t 狗奔跑很快");24 }25 }26 class Cat:Mammal27 {28 public override void bark()29 {30 Console.WriteLine("Cat.bark()\t猫的叫声喵喵喵");31 }32 33 public new void walk()34 {35 Console.WriteLine("Cat.walk()\t 猫行动敏捷");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,由于变量
的值不同而表现出不同的行为,形成了多态性。