1. 问题初现
今天,查看《接口继承的声明问题》一文的反馈,发现Ninputer留下这样一道题:
如果有
class A : Interface1
那么
class B : A, Inteface1
和
class B : A
会出现什么不同的情况呢。编译器在IL级别是用什么手段实现这个功能的呢?
2. 探索问题 & 理解问题
解决问题的过程既是一个探索的过程也是一个推理论证的过程。OK,下面我尝试用反证法来探索这个问题。
首先,我假设问题中B类的两种继承方式有着一样的效果,并试着寻找它们的不一样。为了了解这两种方式的效果,我把上面代码补充完整:
interface IC { }
class A : IC { }
class B1 : A { }
class B2 : A, IC { }
class Program
{
static void Main()
{
A a = new A();
B1 b1 = new B1();
B2 b2 = new B2(); Console.WriteLine(a is IC);
Console.WriteLine(b1 is A);
Console.WriteLine(b1 is IC);
Console.WriteLine(b2 is A);
Console.WriteLine(b2 is IC); }
}代码运行的结果是:
True
True
True
True
True
我们对此结果毫无疑问,那么这是否代表着B1和B2之间没有区别?如果上面的代码作为推理前提在客观上已经足够充分,那么答案是肯定的。但我无法知道论据是否已经达到充分的程度。于是,我把上面的代码修改一下,为类和接口其添加一些成员并观察一下它们所表现出来的行为:
interface IC
{
void M();
}class A : IC
{
void IC.M()
{
Console.WriteLine("In class A");
}
}class B1 : A { }
class B2 : A, IC { }
class Program
{
static void Main()
{
List<IC> cs = new List<IC>();
cs.Add(new A());
cs.Add(new B1());
cs.Add(new B2()); foreach (IC c in cs)
c.M();
}
}程序能够正常编译,运行结果是:
In class A
In class A
In class A
OH, MY GOD! 怎么效果又一样!难道B1跟B2真的没区别??我再把代码修改一下:
interface IC
{
void M();
}class A : IC
{
void IC.M()
{
Console.WriteLine("In class A");
}
}class B1 : A
{
void IC.M()
{
Console.WriteLine("In class B1");
}
}class B2 : A, IC
{
void IC.M()
{
Console.WriteLine("In class B2");
}
}Oh,代码无法编译,编译器发脾气了:
'B1.IC.M()': containing type does implement interface 'IC'
换句话,我们不能再B1里面重新实现IC.M方法,我们只能默默地接受从继类继承而来的那一个了!再修改一下:
interface IC
{
void M();
}class A : IC
{
void IC.M()
{
Console.WriteLine("In class A");
}
}class B1 : A { }
class B2 : A, IC
{
void IC.M()
{
Console.WriteLine("In class B2");
}
}class Program
{
static void Main()
{
List<IC> cs = new List<IC>();
cs.Add(new A());
cs.Add(new B1());
cs.Add(new B2()); foreach (IC c in cs)
c.M();
}
}这些编译正常通过了,得到的结果是:
In class A
In class A
In class B2
3. 得出结论 & 新问题展现
好吧,有结果了,B1和B2两种继承方式的效果的确不同,具体体现在多态行为上(有关多态的介绍,你可以参见《今天你多态了吗?》一文)。B1是个可怜虫,它必须接受A对IC.M的实现,无法改变这种命运;然而B2就不同,它有权选择接受还是拒绝,当然,拒绝的条件是提供有自己特色的实现。
4. 探索新问题 & 解决新问题
那么,我们如何纠正这种非预期的多态行为呢?一个简单的回答就是把B1的声明改成跟B2的一样。但这样,所有继承于A的派生类都必须照做,没得商量!还有其他的办法吗?有的,请先看如下代码:
interface IC
{
void M();
}class A : IC
{
void IC.M()
{
this.M();
} public virtual void M()
{
Console.WriteLine("In class A");
}
}class B1 : A
{
public override void M()
{
Console.WriteLine("In class B1");
}
}class B2 : A, IC
{
public override void M()
{
Console.WriteLine("In class B2");
}
}class Program
{
static void Main()
{
List<IC> cs = new List<IC>();
cs.Add(new A());
cs.Add(new B1());
cs.Add(new B2()); foreach (IC c in cs)
c.M();
}
}运行结果为:
In class A
In class B1
In class B2
这样,多态的效果就如我们所愿了!当然,现在B2声明中的IC又显得有点多余了,但你可以轻松把它拿掉!另外,如果测试程序换成:
class Program
{
static void Main()
{
List<A> ace = new List<A>();
ace.Add(new A());
ace.Add(new B1());
ace.Add(new B2()); foreach (A a in ace)
a.M();
}
}结果还是一样!
5. 继承问题的一些易混淆的地方
请留意下面的代码:
interface IC1 { }
interface IC2 : IC1 { }
class A1 : IC1 { }
class A2 : IC1, IC2 { }
class B1 : A1 { }
class B2 : A1, IC1 { }其中,A1和A2是没有实质的区别的,详细请看《接口继承的声明问题》一文;而B1和B2却在某些场合表现出不同的行为,为何B1和B2会有这种差异,相信现在的你应该有所了解了吧!
6. IL呢?
噢,对了,Ninputer的问题还有个“编译器在IL级别是用什么手段实现这个功能的呢?”!很抱歉,我看了反编译的IL,但未能从中悟出什么道理来,还请Ninputer本人现身说法一番吧。
















