温故而知新-----类和结构 多态性、使用Override和New关键字进行版本控制
多态性
多态性(Polymorphism),是封装和继承之后,面向对象编程的第三个支柱。
多态性具有两个截然不同的方面:
1.在运行时,在方法参数和集合或数组等位置,派生类的对象可以做为基类的对象处理。此时,该对象的声明类型不再与运行是类型相同。
2.基类可以定义并实现虚方法,派生类可以重写这些方法,即派生类提供自己的定义和实现。运行时,客户端调用该方法,CLR查找对象的运行时类型,并调用虚方法的重写方法。
虚方法允许你以统一的方式处理多组先关的对象。
例:基类Shape,派生类Rectangle、Circle
public class Shape
{
public int X{get;set;} public int Y{get;set;} public int Height{get;set;}public int Width{get;set}
//虚方法
pulic virtual void Draw()
{
Console.WriteLine("base");
}
}
class Rectangle:Shape
{
public override void Draw()
{
Console.WriteLine("Rectangle");
}
}
class Circle:Shape
{
public override void Draw()
{
Console.WriteLine("Circle");
}
}
System.Collections.Generic.List<Shape> shapes=new System.Collections.Generic.List<Shape>();
shapes.Add(new Rectangle());
shapes.Add(new Circle());
foreach(Shape s in shapes)
{s.Draw();}
//Output
Rectangle
base
Circle
base
C#中,每个类型都是多态的,因为包括用户定义类型在内的所有类型都继承自Object。
虚成员
当派生类从基类继承时,它会获得基类的所有方法、字段、属性和事件。
派生类的设计器库选择是否1,重写基类的虚拟成员。2.继承最接近的基类方法而不重写它。3.定义隐藏基类实现的成员的新非虚方法(实现)
仅当基类成员声明为virtual或abstract时,派生类才能重写基类成员。派生成员必须使用override关键字显示指示该方法将参与虚调用。
字段不能是虚拟的,只有方法、属性、事件和索引器才可以是虚拟的。当派生类重写某哥虚拟成员时,即使该派生类的实例被当作基类的实例访问,也会调用该成员。
使用新成员隐藏基类成员
如果希望派生成员具有与基类中成员相同的名称,但又不希望派生成员参与虚调用,可以使用new关键字。
public class Base
{
public void Do(){i++};
public int i;
}
public class Der:Base
{
public new void Do(){i++};
public new int i;
}
通过将派生类的实例强制转换为基类的实例,仍然可以从客户端访问隐藏的基类成员。
Der d=new Der();
d.Do();//调用新方法
Base b=(Base)d;
b.Do();//调用基类方法
阻止派生类重写虚拟成员
无论在虚拟成员和最初声明虚拟成员的类之间已经声明了多少个类,虚拟成员永远都是虚拟的。类A声明一个虚拟成员,A派生出B,B派生出C,则C继承该虚拟成员,并且可以选择重写它,而不管B是否为该成员声明了重写。
派生类可以通过将重写声明为sealed来停止虚拟继承。
pulic class C:B
{
public sealed override void Do(){}
}
此后方法Do对从C派生的任何类都不再是虚拟方法。即使他们转换为类型B或A,它对于C的实例仍然是虚拟的。
使用new关键字,密封的方法可以由派生类替换:
public class D:C
{
public new void Do(){}
}
此时,如果在D中使用类型为D的变量调用Do,被调用的将是新的Do。如果使用类型为C、B或A的变量访问D的实例,对Do的调用将遵循虚拟继承的规则,即把这些调用传到类C的Do实现。
从派生类访问基类虚拟成员
已替换或重写的某个方法或属性的派生类仍然可以使用基关键字访问基类的该方法或属性
public class A
{
public virtual void Do(){}
}
public class B:A
{
public override void Do(){base.Do();}
}
使用Override和New关键字进行版本控制
问题引入:基类中引入与派生类中的某个成员具有相同名称的新成员时。。。(意外行为)。
它意味着类必须显示声明某方法还是要重写一个继承方法,还是一个隐藏既有类是名称的继承方法的新方法。
在C#中,派生类可以包含与基类方法同名的方法。
基类方法必须为virtual
如果派生类中的方法前面没有new或override关键字,则编译器将发出警告,该方法将默认有new关键字。
如果派生类中的方法前面带有new关键字,则派生类的对象将调用该方法,而不是调用基类方法。
可以从派生类中使用base关键字调用基类方法
override、virtual和new关键字还可以用于属性、索引器和事件中。
实际项目中:
创建了一个A类
class A
{
public virtual void Do(){}
public virtual void E(){}
}
class Der:A
{
public void Fo(){}
}
发布新版本时,更新了A类的新版本
class A
{
public virtual void Do(){}
public virtual void E(){}
public void Fo(){}
}
现在,A类的新版本中包含名为Fo的方法。开始,没有出现任何问题。但是,一旦使用A类的新版本重新编译应用程序,编译器警告CS0108.此警告提示必须考虑希望Fo方法在应用程序中的工作方式。
如果希望自己的方法重写新的基类方法,使用override关键字:
class Der:A
{
public override void Fo(){}
}
override确保派生自Der的任何对象都将使用A的派生类版本。派生自Der的对象仍可以使用基关键字base访问A的基类版本:
base,Fo();
如果不希望自己的方法重写新的基类方法,要注意:
为了避免两个方法之间发生混淆,可以重命名自己的方法。(耗时,易出错,在某些情况下不可行!项目小的话,可以使用VS的重构选项来重命名)。
或者也可以在派生类中使用new关键字来防止出现该警告:
class Der:A
{
public new void Fo(){}
}//隐藏基类中包含的定义。
重写和方法选择
在类中置顶方法时,可能会多个方法与调用兼容。
例:
public class Der:Base
{
public override void Do(int param){}
public void Do(double param){}
}
在Der的一个实例中调用Do方法时,C#编译器将首先尝试使该调用与最初在Der上声明的Do版本兼容。重写方法不被视为是在类上进行声明的,而是在基类上声明的新实现。仅当C#编译器无法将方法调用与Der上的原始方法匹配时,它才尝试将调用与具有相同名称和兼容参数的重写方法匹配。
例:int val=1; Der d=new Der(); d.Do(val);//调用Do(double)
由于变量val可以隐式转换为double类型,因此C#编译器将调用Do(double),而不是Do(int)。
有两中方法可以避免此情况。1.避免将新方法声明为与虚方法同名。2.可以通过将Der的实例强制转换为Base来使C#编译器搜索基类方法列表,从而使其调用虚方法。由于是虚方法,因此将调用Der上的Do(int)的实现。
((Base)d).Do(val);//在Der上调用Do(int)