我是谁?[C#]

博客围绕C#中接口实例化问题展开。探讨了ABC t = new Class1()究竟实例化了什么,指出t指向Class1实例。还分析了t.N()不能编译的原因,是接口的“选择性透过”特性屏蔽部分功能。最后强调要理解使用接口的意义,避免误解。

我是谁?[C#]

Written by Allen Lee

0. xuzicn 提出了这样一个问题

有一个 interface ABC 包括了如下的方法 M():

None.gif public interface ABC
ExpandedBlockStart.gifContractedBlock.gif
dot.gif {
InBlock.gif
void M();
ExpandedBlockEnd.gif}

另外有个类 Class1 继承了 ABC 并且拥有自己的方法 N():

None.gif public class Class1:ABC
ExpandedBlockStart.gifContractedBlock.gif
dot.gif {
ExpandedSubBlockStart.gifContractedSubBlock.gif
public Class1() dot.gif{}
InBlock.gif
InBlock.gif
public void M()
ExpandedSubBlockStart.gifContractedSubBlock.gif
dot.gif{
InBlock.gifConsole.WriteLine(
" NowweareinClass1,usingmethodwhichinheritfromInterfaceABC " );
ExpandedSubBlockEnd.gif}

InBlock.gif
InBlock.gif
public void N()
ExpandedSubBlockStart.gifContractedSubBlock.gif
dot.gif{
InBlock.gifConsole.WriteLine(
" NowweareinClass1,weusedmethodwhichnotinheritfromInterfaceABC " );
ExpandedSubBlockEnd.gif}

ExpandedBlockEnd.gif}

程序中有如下语句:

None.gif // Code#01
None.gif

None.gifABCt
= new Class1();
None.gift.M();

这种情况下编译可以通过而且会输出正确的结果。但是如果是下面的情况:

None.gif // Code#02
None.gif

None.gifABCt
= new Class1();
None.gift.N();

编译就会出错,所以通过 ABC t = new Class1() 这句话我们得到的肯定不是 Class1 的一个实例。但是接口是不可以实例化的,那么 ABC t = new Class1() 究竟实例化了一个什么东西?

1. 我是谁?

很久以前,成龙上演了一部《我是谁》,现在 t 也遭遇了相同的问题。成龙当时就不太幸运了,因为要唤醒人的记忆不是那么容易;相比之下,t 就幸运的多了,因为它有元数据这张 ID 卡。那么,t 究竟是谁?

None.gif // Code#03
None.gif

None.gifABCt
= new Class1();
None.gifConsole.WriteLine(t.GetType());

你猜答案是什么?是 Class1!

2. 选择性透过...

既然 t 指向 Class1 的实例,为什么 t.N() 不能通过编译呢?如果你玩过 Flash,你会知道 Flash 的 Mask 可以做出类似于聚光灯的效果,把聚光圈以外的东西统统隐藏起来。从目标对象的角度来看,接口利用这种“遮掩”的特性“屏蔽”了目标对象的部分功能;从客户端的角度来看,接口就像细胞膜那样有选择性的让目标对象的功能进入客户端的作用范围。

请看下图:

o_01.JPG

对应 Code #01 和 Code #02,t 发挥了接口的“选择性透过”特性,仅让客户端和 Class1 实例的 M() 接触。

3. 噢,别这样!

虽然 Code #02 中的 t 确实指向了 Class1 的实例,但编译器却不允许你调用 t.N(),因为 ABC 并不认识 Class1.N()。不过我们心知肚明 t 是有能力调用 N() 的,于是就去和编译器沟通一下。最后我们和编译器达成共识,让 t 这样调用 N():

None.gif // Code#04
None.gif

None.gif((Class1)t).N();

正当你为让 t 发挥了潜能而高兴时,你却听到另外一种声音:别这样!为什么?假如我们有这样一个方法:

None.gif // Code#05
None.gif

None.gif
void Process(ABCabc)
ExpandedBlockStart.gifContractedBlock.gif
dot.gif {
InBlock.gif((Class1)abc).N();
ExpandedBlockEnd.gif}

某天不知道谁搞了一个 Class2:

None.gif // Code#06
None.gif

None.gif
class Class2:ABC
ExpandedBlockStart.gifContractedBlock.gif
dot.gif {
InBlock.gif
public void M()
ExpandedSubBlockStart.gifContractedSubBlock.gif
dot.gif{
InBlock.gifConsole.WriteLine(
" NowweareinClass1,usingmethodwhichinheritfromInterfaceABC " );
ExpandedSubBlockEnd.gif}

InBlock.gif
InBlock.gif
public void O()
ExpandedSubBlockStart.gifContractedSubBlock.gif
dot.gif{
InBlock.gifConsole.WriteLine(
" Ouch!Areyouexpectingme? " );
ExpandedSubBlockEnd.gif}

ExpandedBlockEnd.gif}

然后这个人把 Class2 的实例传递给 Process():

None.gif // Code#07
None.gif

None.gifABCt
= new Class2();
None.gifProcess(t);

那个人当然不知道我们在 Process 里面搞什么鬼,但却莫名其妙的得到一个 InvalidCastException!

好吧,如果你需要在 Process 里面处理 Class1 的实例,那你为什么不直接把你的想法告诉别人呢?

None.gif // Code#08
None.gif

None.gifProcess(Class1c)
ExpandedBlockStart.gifContractedBlock.gif
dot.gif {
InBlock.gifc.N();
ExpandedBlockEnd.gif}

同样的道理,如果我们希望调用 Class1.N(),为什么我们不直接做:

None.gif // Code#09
None.gif

None.gifClass1c
= new Class1();
None.gifc.N();

而要写 Code #02 那样的代码呢?

回顾 Code #05,我们除了知道 abc 肯定有一个 M() 之外,根本无从得知具体的实例还会有别的什么方法可用,那我们凭什么要求 Code #02 可以通过编译呢?

4. 我还应该说点什么呢?

好吧,我们绕了一个大圈,最后发现虽然 ABC t = new Class1() 无法通过编译,但 t 确实指向一个 Class1 实例,只是这个实例好像被 t 搞得有点“低能儿”罢了。我认为真正的问题并不在于 t 究竟是什么,而是在于我们为什么要这样使用接口。这是一个很基础的问题,除非你不打算在 OO 方面有所发展,否则你得把它弄清楚,要不然你真的会很麻烦,至少你会误解接口的真正意义。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值