对于继承实例化的四种情况实际开发中遇到的挺多的,其中父类声明子类实现的情况,比如说Father f=new Sun();这种情况得到的到底是父类的实例还是子类的实例呢.
先看下面这个例子:
三个类,一个父类,一个子类,一个包含Main()函数的测试类;
1.父类:
using System;
namespace TestInherit
{
/// <summary>
/// Father 的摘要说明。
/// </summary>
public class Father
{
public virtual string returnStringAAA()
{
return "Father_AAA";
}
public string returnBBB()
{
return "Father_BBB";
}
}
}
2.子类
using System;
namespace TestInherit
{
/// <summary>
/// Sun 的摘要说明。
/// </summary>
public class Sun:Father
{
public override string returnStringAAA()
{
//return base.returnStringAAA ();
return "Sun_AAA";
}
public string returnBBB()
{
return "Sun_BBB";
}
}
}
3.测试类
using System;
namespace TestInherit
{
/// <summary>
/// Class1 的摘要说明。
/// </summary>
class Class1
{
/// <summary>
/// 应用程序的主入口点。
/// </summary>
[STAThread]
static void Main(string[] args)
{
//
// TODO: 在此处添加代码以启动应用程序
//
父类声明子类实现///
Father father1=new Sun ();
father1.returnStringAAA();
father1.returnBBB();
子类声明子类实现//
Sun sun2=new Sun ();
sun2.returnStringAAA();
sun2.returnBBB();
父类声明父类实现/
Father father2=new Father ();
father2.returnStringAAA();
father2.returnBBB();
///子类声明父类实现//
Sun sun3=((Sun)new Father ());
sun3.returnStringAAA();
sun3.returnBBB();
}
}
}
首先作个说明,上面的类编译的时候不会报错,但是会出一个警告:“TestInherit.Sun.returnBBB()”上要求关键字 new,因为它隐藏了继承成员“TestInherit.Father.returnBBB()”,因为两个类里面有两个同名的方法,和returnStringAAA()方法是完全不一样的,returnStringAAA()方法是子类重写(override)父类中的虚方法.加上new 关键字后编译完全通过,其结果和不加new得情况一样的,不加new其实也隐藏了父类中的同名方法.
其中第二种,第三种情况比较简单:
Sun sun2=new Sun ();得到的是子类的实例,结果当然是
"Sun_AAA";
"Sun_BBB";
Father father2=new Father ();得到的是父类的实例,结果为
"Father_AAA";
"Father_BBB";
最后一种情况,会出"System.InvalidCastException(指定的转换无效)"的异常.仔细想一想,确实这样.举个例子vcd是父类,dvd是子类,子类dvd扩充了父类vcd的功能,vcd能播放的dvd一定能放,但是想让vcd播放dvd的碟片那就有可能会出问题了吧,可能不太恰当,但是原理就是这样的.
对于第一种情况,
Father father1=new Sun ();,得到的结果什么呢?
结果是这样的:
"Sun_AAA";
"Father_BBB";
其中"Sun_AAA"比较清楚,但是"Father_BBB"是为什么呢?开始我认为是father1是Father的实例,父类中的方法和属性对其可见,所以结果是"Father_BBB";
如果上面的例子是这样呢?
((Sun)father1).returnBBB();那得到的结果又是什么呢?
结果是"Sun_BBB",这样看来其实"父类声明,子类实现"得到的是子的实例;如果照开始的想法,是父的实例的话,其实在上面强制类型转化的时候可能就会像最后一种情况一样,出"指定的转换无效"的异常了.所以像我们平时经常会用到的IList list=new ArrayList();毫无疑问就是ArrayList的实例,再说IList也是不能被直接实例化的.在上面的例子中,那样实例化的时候,其实父屏蔽了子类中的returnBBB()方法,而强制类型转化为Sun以后,Sun里面的returnBBB()又隐藏了父类中的同名的方法,所以出现了上面的结果.
有误之处,欢迎大家指正!!
最近又看到Allen Lee 的一篇文章"我是谁?",其实讲得也是这个问题,比我理解得更深入,在下面贴出来大家再学习一下.
有一个 interface ABC 包括了如下的方法 M():
public interface ABC
{
void M();
}
另外有个类 Class1 继承了 ABC 并且拥有自己的方法 N():
public class Class1 : ABC
{
public Class1(){}
public void M()
{
Console.WriteLine("Now we are in Class1, using method which inherit from Interface ABC ");
}
public void N()
{
Console.WriteLine("Now we are in Class1, we used method which not inherit from Interface ABC ");
}
}
程序中有如下语句:
// Code #01
ABC t = new Class1();
t.M();
这种情况下编译可以通过而且会输出正确的结果。但是如果是下面的情况:
// Code #02
ABC t = new Class1();
t.N();
编译就会出错,所以通过 ABC t = new Class1() 这句话我们得到的肯定不是 Class1 的一个实例。但是接口是不可以实例化的,那么 ABC t = new Class1() 究竟实例化了一个什么东西?
1. 我是谁?
很久以前,成龙上演了一部《我是谁》,现在 t 也遭遇了相同的问题。成龙当时就不太幸运了,因为要唤醒人的记忆不是那么容易;相比之下,t 就幸运的多了,因为它有元数据这张 ID 卡。那么,t 究竟是谁?
// Code #03
ABC t = new Class1();
Console.WriteLine(t.GetType());
你猜答案是什么?是 Class1!
2. 选择性透过...
既然 t 指向 Class1 的实例,为什么 t.N() 不能通过编译呢?如果你玩过 Flash,你会知道 Flash 的 Mask 可以做出类似于聚光灯的效果,把聚光圈以外的东西统统隐藏起来。从目标对象的角度来看,接口利用这种“遮掩”的特性“屏蔽”了目标对象的部分功能;从客户端的角度来看,接口就像细胞膜那样有选择性的让目标对象的功能进入客户端的作用范围。
请看下图:
(图没贴出来)
对应 Code #01 和 Code #02,t 发挥了接口的“选择性透过”特性,仅让客户端和 Class1 实例的 M() 接触。
3. 噢,别这样!
虽然 Code #02 中的 t 确实指向了 Class1 的实例,但编译器却不允许你调用 t.N(),因为 ABC 并不认识 Class1.N()。不过我们心知肚明 t 是有能力调用 N() 的,于是就去和编译器沟通一下。最后我们和编译器达成共识,让 t 这样调用 N():
// Code #04
((Class1)i).N();
正当你为让 t 发挥了潜能而高兴时,你却听到另外一种声音:别这样!为什么?假如我们有这样一个方法:
// Code #05
void Process(ABC abc)
{
((Class1)abc).N();
}
某天不知道谁搞了一个 Class2:
// Code #06
class Class2 : ABC
{
public void M()
{
Console.WriteLine("Now we are in Class1, using method which inherit from Interface ABC ");
}
public void O()
{
Console.WriteLine("Ouch! Are you expecting me?");
}
}
然后这个人把 Class2 的实例传递给 Process():
// Code #07
ABC t = new Class2();
Process(t);
那个人当然不知道我们在 Process 里面搞什么鬼,但却莫名其妙的得到一个 InvalidCastException!
好吧,如果你需要在 Process 里面处理 Class1 的实例,那你为什么不直接把你的想法告诉别人呢?
// Code #08
Process(Class1 c)
{
c.N();
}
同样的道理,如果我们希望调用 Class1.N(),为什么我们不直接做:
// Code #09
Class1 c = new Class1();
c.N();
而要写 Code #02 那样的代码呢?
回顾 Code #05,我们除了知道 abc 肯定有一个 M() 之外,根本无从得知具体的实例还会有别的什么方法可用,那我们凭什么要求 Code #02 可以通过编译呢?
4. 我还应该说点什么呢?
好吧,我们绕了一个大圈,最后发现虽然 ABC t = new Class1() 无法通过编译,但 t 确实指向一个 Class1 实例,只是这个实例好像被 t 搞得有点“低能儿”罢了。我认为真正的问题并不在于 t 究竟是什么,而是在于我们为什么要这样使用接口。这是一个很基础的问题,除非你不打算在 OO 方面有所发展,否则你得把它弄清楚,要不然你真的会很麻烦,至少你会误解接口的真正意义。