C#易错易混淆知识总结(六)--{里氏转换原则}{虚方法}
一 里氏转换原则
1:里氏转换
1)子类可以赋值给父类(如果一个地方需要一个父类作为参数,我们可以给一个子类代替)
2)如果父类中装的是子类对象,那么可以将这个父类强转为子类对象。
【子类对象可以调用父类中的成员,但是父类对象永远都只能调用自己的成员】
解释:比如这个Join方法,第二个参数是object类型的数组,此时object类是所有类型的基类,所以这里任何类型的数组都可以。此时体现了(如果一个地方需要一个父类作为参数,我们可以给一个子类代替)
// // 摘要: // 串联对象数组的各个元素,其中在每个元素之间使用指定的分隔符。 // // 参数: // separator: // 要用作分隔符的字符串。只有在 values 具有多个元素时,separator 才包括在返回的字符串中。 // // values: // 一个数组,其中包含要连接的元素。 // // 返回结果: // 一个由 values 的元素组成的字符串,这些元素以 separator 字符串分隔。如果 values 为空数组,该方法将返回 System.String.Empty。 // // 异常: // T:System.ArgumentNullException: // values 为 null。 [ComVisible(false)] public static String Join(String separator, params object[] values);
string.Join("+",new int[]{ 1,1,1,1,1,1}); string.Join("-", new string[] { "a","c","y","y","q","y"});
如果父类中装的是子类对象,那么可以将这个父类强转为子类对象。
static void Main(string[] args)
{
Person p = new Student();
Student sd = (Student)p;
sd.StudentSayHello();
Console.ReadKey();
}
public class Person { public void PersonSayHello() { Console.WriteLine("我是父类"); } } public class Student : Person { public void StudentSayHello() { Console.WriteLine("我是学生"); } }
结果:
2:is和as
is表示类型转换,如果能够转换成功,则返回一个true,否则返回一个false
if (p is Student) //is表示类型转换,如果能够转换成功,则返回一个true,否则返回一个false { Student sd = (Student)p; sd.StudentSayHello(); } else { Console.WriteLine("p中不是装有student"); } Console.ReadKey();
as表示类型转换,如果能够转换则返回对应的对象,否则返回一个null。
Teacher t = p as Teacher; //不能转换成功
此时调试发现t等于null
***********************************************************************
为什么子类可以替换父类的位置,而程序的功能不受影响呢?
当满足继承的时候,父类肯定存在非私有成员,子类肯定是得到了父类的这些非私有成员(假设,父类的的成员全部是私有的,那么子类没办法从父类继承任何成员,也就不存在继承的概念了)。既然子类继承了父类的这些非私有成员,那么父类对象也就可以在子类对象中调用这些非私有成员。所以,子类对象可以替换父类对象的位置。
【这里我有一个误区,就是一定要知道父类中的私有成员出了这个类外面哪儿怕是父类都是调用不到的】
class Program { static void Main(string[] args) {
Person p = new Person();
p.Say();
Person p1 = new Student();
p1.Say();
Console.ReadKey();
} } class Person { //父类的私有成员 private int nAge; public Person() { Console.WriteLine("我是Person构造函数,我是一个人!"); } public void Say() { Console.WriteLine("我是一个人!"); } } class Student : Person { public Student() { Console.WriteLine("我是Student构造函数,我是一个学生!"); } public void SayStude() { Console.WriteLine("我是一个学生!"); } } class SeniorStudent : Student { public SeniorStudent() { Console.WriteLine("我是SeniorStudent构造函数,我是一个高中生!"); } public void SaySenior() { Console.WriteLine("我是一个高中生!"); } }
在访问的过程中,可以发现p只可以访问父类的say,而p1也只可以访问父类的Say方法。
那么它们在内存中发生了些什么呢?如下图:
二,虚方法
使用virtual关键字修饰的方法,叫做虚方法(一般都是在父类中)。
class Person { private int nAge; public Person() { Console.WriteLine("我是Person构造函数,我是一个人!"); } //这里定义了一个虚方法 public virtual void Say() { Console.WriteLine("我是一个人!"); } } class Student : Person { //子类使用override关键字改写了父类的虚方法 public override void Say() { Console.WriteLine("我是一个学生!"); } public Student() { Console.WriteLine("我是Student构造函数,我是一个学生!"); } public void SayStude() { Console.WriteLine("我是一个学生!"); } }
static void Main(string[] args)
{
Person p = new Person();
p.Say();
Person p1 = new Student();
p1.Say();
Student s = new Student();
s.Say();
Console.ReadKey();
}
打印结果如下:
我们很明显的可以发现,第二个表达式满足里氏替换原则,p1.Say()执行的应该是父类的Say()方法,但是这里却执行了子类的Say()方法。
这就是子类使用override关键字的Say()方法覆盖了父类的用Virtual关键字修饰的Say()方法。
那么如果父类使用virtual关键字修饰,而子类没有重写该方法时会怎么样呢?
如果子类找不到override方法,则会回溯到该子类的父类去找是否有override方法,知道回溯到自身的虚方法,并执行。
也就是说跟没加之前一样。