基类与接口混合继承的声明问题 [C#, Design]

本文围绕C#中基类与接口混合继承的声明问题展开。通过反证法探索两种继承方式效果,发现其在多态行为上存在差异。还探讨了纠正非预期多态行为的方法,最后提及在无法修改基类源码时,可使用接口重新映射技巧。

基类与接口混合继承的声明问题 [C#, Design]

Updated on Friday, November 19, 2004

Written by Allen Lee

1. 问题初现

今天,查看《接口继承的声明问题》一文的反馈,发现Ninputer留下这样一道题:

如果有

class A : Interface1

那么

class B : A, Inteface1

class B : A

会出现什么不同的情况呢。编译器在IL级别是用什么手段实现这个功能的呢?

2. 探索问题 & 理解问题

解决问题的过程既是一个探索的过程也是一个推理论证的过程。OK,下面我尝试用反证法来探索这个问题。

首先,我假设问题中B类的两种继承方式有着一样的效果,并试着寻找它们的不一样。为了了解这两种方式的效果,我把上面代码补充完整:

ExpandedBlockStart.gif ContractedBlock.gif interface IC dot.gif {}
None.gif
ExpandedBlockStart.gifContractedBlock.gif
class A:IC dot.gif {}
None.gif
ExpandedBlockStart.gifContractedBlock.gif
class B1:A dot.gif {}
None.gif
ExpandedBlockStart.gifContractedBlock.gif
class B2:A,IC dot.gif {}
None.gif
None.gif
class Program
ExpandedBlockStart.gifContractedBlock.gif
dot.gif {
InBlock.gif
staticvoidMain()
ExpandedSubBlockStart.gifContractedSubBlock.gif
dot.gif{
InBlock.gifAa
=newA();
InBlock.gifB1b1
=newB1();
InBlock.gifB2b2
=newB2();
InBlock.gif
InBlock.gifConsole.WriteLine(a
isIC);
InBlock.gifConsole.WriteLine(b1
isA);
InBlock.gifConsole.WriteLine(b1
isIC);
InBlock.gifConsole.WriteLine(b2
isA);
InBlock.gifConsole.WriteLine(b2
isIC);
InBlock.gif
ExpandedSubBlockEnd.gif}

ExpandedBlockEnd.gif}

代码运行的结果是:

  • True
  • True
  • True
  • True
  • True

我们对此结果毫无疑问,那么这是否代表着B1和B2之间没有区别?如果上面的代码作为推理前提在客观上已经足够充分,那么答案是肯定的。但我无法知道论据是否已经达到充分的程度。于是,我把上面的代码修改一下,为类和接口其添加一些成员并观察一下它们所表现出来的行为:

None.gif interface IC
ExpandedBlockStart.gifContractedBlock.gif
dot.gif {
InBlock.gif
voidM();
ExpandedBlockEnd.gif}

None.gif
None.gif
class A:IC
ExpandedBlockStart.gifContractedBlock.gif
dot.gif {
InBlock.gif
voidIC.M()
ExpandedSubBlockStart.gifContractedSubBlock.gif
dot.gif{
InBlock.gifConsole.WriteLine(
"InclassA");
ExpandedSubBlockEnd.gif}

ExpandedBlockEnd.gif}

None.gif
ExpandedBlockStart.gifContractedBlock.gif
class B1:A dot.gif {}
None.gif
ExpandedBlockStart.gifContractedBlock.gif
class B2:A,IC dot.gif {}
None.gif
None.gif
class Program
ExpandedBlockStart.gifContractedBlock.gif
dot.gif {
InBlock.gif
staticvoidMain()
ExpandedSubBlockStart.gifContractedSubBlock.gif
dot.gif{
InBlock.gifList
IC>cs=newListIC>();
InBlock.gifcs.Add(
newA());
InBlock.gifcs.Add(
newB1());
InBlock.gifcs.Add(
newB2());
InBlock.gif
InBlock.gif
foreach(ICcincs)
InBlock.gifc.M();
ExpandedSubBlockEnd.gif}

ExpandedBlockEnd.gif}

程序能够正常编译,运行结果是:

  • In class A
  • In class A
  • In class A

OH, MY GOD! 怎么效果又一样!难道B1跟B2真的没区别??我再把代码修改一下:

None.gif interface IC
ExpandedBlockStart.gifContractedBlock.gif
dot.gif {
InBlock.gif
voidM();
ExpandedBlockEnd.gif}

None.gif
None.gif
class A:IC
ExpandedBlockStart.gifContractedBlock.gif
dot.gif {
InBlock.gif
voidIC.M()
ExpandedSubBlockStart.gifContractedSubBlock.gif
dot.gif{
InBlock.gifConsole.WriteLine(
"InclassA");
ExpandedSubBlockEnd.gif}

ExpandedBlockEnd.gif}

None.gif
None.gif
class B1:A
ExpandedBlockStart.gifContractedBlock.gif
dot.gif {
InBlock.gif
voidIC.M()
ExpandedSubBlockStart.gifContractedSubBlock.gif
dot.gif{
InBlock.gifConsole.WriteLine(
"InclassB1");
ExpandedSubBlockEnd.gif}

ExpandedBlockEnd.gif}

None.gif
None.gif
class B2:A,IC
ExpandedBlockStart.gifContractedBlock.gif
dot.gif {
InBlock.gif
voidIC.M()
ExpandedSubBlockStart.gifContractedSubBlock.gif
dot.gif{
InBlock.gifConsole.WriteLine(
"InclassB2");
ExpandedSubBlockEnd.gif}

ExpandedBlockEnd.gif}

Oh,代码无法编译,编译器发脾气了:

'B1.IC.M()': containing type does implement interface 'IC'

换句话,我们不能再B1里面重新实现IC.M方法,我们只能默默地接受从继类继承而来的那一个了!再修改一下:

None.gif interface IC
ExpandedBlockStart.gifContractedBlock.gif
dot.gif {
InBlock.gif
voidM();
ExpandedBlockEnd.gif}

None.gif
None.gif
class A:IC
ExpandedBlockStart.gifContractedBlock.gif
dot.gif {
InBlock.gif
voidIC.M()
ExpandedSubBlockStart.gifContractedSubBlock.gif
dot.gif{
InBlock.gifConsole.WriteLine(
"InclassA");
ExpandedSubBlockEnd.gif}

ExpandedBlockEnd.gif}

None.gif
ExpandedBlockStart.gifContractedBlock.gif
class B1:A dot.gif {}
None.gif
None.gif
class B2:A,IC
ExpandedBlockStart.gifContractedBlock.gif
dot.gif {
InBlock.gif
voidIC.M()
ExpandedSubBlockStart.gifContractedSubBlock.gif
dot.gif{
InBlock.gifConsole.WriteLine(
"InclassB2");
ExpandedSubBlockEnd.gif}

ExpandedBlockEnd.gif}

None.gif
None.gif
class Program
ExpandedBlockStart.gifContractedBlock.gif
dot.gif {
InBlock.gif
staticvoidMain()
ExpandedSubBlockStart.gifContractedSubBlock.gif
dot.gif{
InBlock.gifList
IC>cs=newListIC>();
InBlock.gifcs.Add(
newA());
InBlock.gifcs.Add(
newB1());
InBlock.gifcs.Add(
newB2());
InBlock.gif
InBlock.gif
foreach(ICcincs)
InBlock.gifc.M();
ExpandedSubBlockEnd.gif}

ExpandedBlockEnd.gif}

这些编译正常通过了,得到的结果是:

  • In class A
  • In class A
  • In class B2

3. 得出结论 & 新问题展现

好吧,有结果了,B1和B2两种继承方式的效果的确不同,具体体现在多态行为上(有关多态的介绍,你可以参见《今天你多态了吗?》一文)。B1是个可怜虫,它必须接受A对IC.M的实现,无法改变这种命运;然而B2就不同,它有权选择接受还是拒绝,当然,拒绝的条件是提供有自己特色的实现。

4. 探索新问题 & 解决新问题

那么,我们如何纠正这种非预期的多态行为呢?一个简单的回答就是把B1的声明改成跟B2的一样。但这样,所有继承于A的派生类都必须照做,没得商量!还有其他的办法吗?有的,请先看如下代码:

None.gif interface IC
ExpandedBlockStart.gifContractedBlock.gif
dot.gif {
InBlock.gif
voidM();
ExpandedBlockEnd.gif}

None.gif
None.gif
class A:IC
ExpandedBlockStart.gifContractedBlock.gif
dot.gif {
InBlock.gif
voidIC.M()
ExpandedSubBlockStart.gifContractedSubBlock.gif
dot.gif{
InBlock.gif
this.M();
ExpandedSubBlockEnd.gif}

InBlock.gif
InBlock.gif
publicvirtualvoidM()
ExpandedSubBlockStart.gifContractedSubBlock.gif
dot.gif{
InBlock.gifConsole.WriteLine(
"InclassA");
ExpandedSubBlockEnd.gif}

ExpandedBlockEnd.gif}

None.gif
None.gif
class B1:A
ExpandedBlockStart.gifContractedBlock.gif
dot.gif {
InBlock.gif
publicoverridevoidM()
ExpandedSubBlockStart.gifContractedSubBlock.gif
dot.gif{
InBlock.gifConsole.WriteLine(
"InclassB1");
ExpandedSubBlockEnd.gif}

ExpandedBlockEnd.gif}

None.gif
None.gif
class B2:A,IC
ExpandedBlockStart.gifContractedBlock.gif
dot.gif {
InBlock.gif
publicoverridevoidM()
ExpandedSubBlockStart.gifContractedSubBlock.gif
dot.gif{
InBlock.gifConsole.WriteLine(
"InclassB2");
ExpandedSubBlockEnd.gif}

ExpandedBlockEnd.gif}

None.gif
None.gif
class Program
ExpandedBlockStart.gifContractedBlock.gif
dot.gif {
InBlock.gif
staticvoidMain()
ExpandedSubBlockStart.gifContractedSubBlock.gif
dot.gif{
InBlock.gifList
IC>cs=newListIC>();
InBlock.gifcs.Add(
newA());
InBlock.gifcs.Add(
newB1());
InBlock.gifcs.Add(
newB2());
InBlock.gif
InBlock.gif
foreach(ICcincs)
InBlock.gifc.M();
ExpandedSubBlockEnd.gif}

ExpandedBlockEnd.gif}

运行结果为:

  • In class A
  • In class B1
  • In class B2

这样,多态的效果就如我们所愿了!当然,现在B2声明中的IC又显得有点多余了,但你可以轻松把它拿掉!另外,如果测试程序换成:

None.gif class Program
ExpandedBlockStart.gifContractedBlock.gif
dot.gif {
InBlock.gif
staticvoidMain()
ExpandedSubBlockStart.gifContractedSubBlock.gif
dot.gif{
InBlock.gifList
A>ace=newListA>();
InBlock.giface.Add(
newA());
InBlock.giface.Add(
newB1());
InBlock.giface.Add(
newB2());
InBlock.gif
InBlock.gif
foreach(Aainace)
InBlock.gifa.M();
ExpandedSubBlockEnd.gif}

ExpandedBlockEnd.gif}

结果还是一样!

5. 是的,我说谎了。[New]

或许你已经注意到,在上面的整个过程中,我做了一个最大的假设,那就是我可以任我喜欢修改A的源代码!也因为这样,我可以轻松的纠正这些非预期的多态行为。但实际的情况是,我们不会每次都那么幸运。如果我们仅仅得到一个包含类A和接口IC的程序集呢?那么,我们就需要使用到接口的重新映射了。实际上,B2就是使用这种技巧。还是让我们来看看具体的情况:

  1. 接口IC的规格不变。
  2. 我们只知道类A的声明以及它的成员列表和对应的输出:
Class
class A : IC
Output
Method
public void M();
In class A
Method
void IC.M();
In class A

现在我需要实现一批继承于A的派生类,但我不希望同时继承A的对应方法的实现,我该怎么做?很简单,首先创建一个类AX继承自类A和接口IC,并在AX里面处理好相关的事宜,接着让那批派生类继承于AX:

None.gif class AX:A,IC
ExpandedBlockStart.gifContractedBlock.gif
dot.gif {
InBlock.gif
//这里使用new是声明其与基类的同名方法M没有任何瓜葛。
InBlock.gif
//使用virtual是为后代的继承打下铺垫。
InBlock.gif

InBlock.gif
publicnewvirtualvoidM()
ExpandedSubBlockStart.gifContractedSubBlock.gif
dot.gif{
InBlock.gifConsole.WriteLine(
"InclassAX");
ExpandedSubBlockEnd.gif}

InBlock.gif
InBlock.gif
voidIC.M()
ExpandedSubBlockStart.gifContractedSubBlock.gif
dot.gif{
InBlock.gif
this.M();
ExpandedSubBlockEnd.gif}

ExpandedBlockEnd.gif}

None.gif
None.gif
class B1:AX
ExpandedBlockStart.gifContractedBlock.gif
dot.gif {
InBlock.gif
publicoverridevoidM()
ExpandedSubBlockStart.gifContractedSubBlock.gif
dot.gif{
InBlock.gifConsole.WriteLine(
"InclassB1");
ExpandedSubBlockEnd.gif}

ExpandedBlockEnd.gif}

None.gif
None.gif
class B2:AX
ExpandedBlockStart.gifContractedBlock.gif
dot.gif {
InBlock.gif
publicoverridevoidM()
ExpandedSubBlockStart.gifContractedSubBlock.gif
dot.gif{
InBlock.gifConsole.WriteLine(
"InclassB2");
ExpandedSubBlockEnd.gif}

ExpandedBlockEnd.gif}

好吧,然我们来看看测试程序:

None.gif class Program
ExpandedBlockStart.gifContractedBlock.gif
dot.gif {
InBlock.gif
staticvoidMain(string[]args)
ExpandedSubBlockStart.gifContractedSubBlock.gif
dot.gif{
InBlock.gifList
IC>cs=newListIC>();
InBlock.gifcs.Add(
newA());
InBlock.gifcs.Add(
newAX());
InBlock.gifcs.Add(
newB1());
InBlock.gifcs.Add(
newB2());
InBlock.gif
InBlock.gif
foreach(ICcincs)
html>
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值