基类与接口混合继承的声明问题 [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类的两种继承方式有着一样的效果,并试着寻找它们的不一样。为了了解这两种方式的效果,我把上面代码补充完整:
interfaceIC{}
classA:IC{}
classB1:A{}
classB2:A,IC{}
classProgram
{
staticvoidMain()
{
Aa=newA();
B1b1=newB1();
B2b2=newB2();
Console.WriteLine(aisIC);
Console.WriteLine(b1isA);
Console.WriteLine(b1isIC);
Console.WriteLine(b2isA);
Console.WriteLine(b2isIC);
}
}
代码运行的结果是:
我们对此结果毫无疑问,那么这是否代表着B1和B2之间没有区别?如果上面的代码作为推理前提在客观上已经足够充分,那么答案是肯定的。但我无法知道论据是否已经达到充分的程度。于是,我把上面的代码修改一下,为类和接口其添加一些成员并观察一下它们所表现出来的行为:
interfaceIC
{
voidM();
}
classA:IC
{
voidIC.M()
{
Console.WriteLine("InclassA");
}
}
classB1:A{}
classB2:A,IC{}
classProgram
{
staticvoidMain()
{
ListIC>cs=newListIC>();
cs.Add(newA());
cs.Add(newB1());
cs.Add(newB2());
foreach(ICcincs)
c.M();
}
}
程序能够正常编译,运行结果是:
-
In class A
-
In class A
- In class A
OH, MY GOD! 怎么效果又一样!难道B1跟B2真的没区别??我再把代码修改一下:
interfaceIC
{
voidM();
}
classA:IC
{
voidIC.M()
{
Console.WriteLine("InclassA");
}
}
classB1:A
{
voidIC.M()
{
Console.WriteLine("InclassB1");
}
}
classB2:A,IC
{
voidIC.M()
{
Console.WriteLine("InclassB2");
}
}
Oh,代码无法编译,编译器发脾气了:
'B1.IC.M()': containing type does implement interface 'IC' |
换句话,我们不能再B1里面重新实现IC.M方法,我们只能默默地接受从继类继承而来的那一个了!再修改一下:
interfaceIC
{
voidM();
}
classA:IC
{
voidIC.M()
{
Console.WriteLine("InclassA");
}
}
classB1:A{}
classB2:A,IC
{
voidIC.M()
{
Console.WriteLine("InclassB2");
}
}
classProgram
{
staticvoidMain()
{
ListIC>cs=newListIC>();
cs.Add(newA());
cs.Add(newB1());
cs.Add(newB2());
foreach(ICcincs)
c.M();
}
}
这些编译正常通过了,得到的结果是:
-
In class A
-
In class A
- In class B2
3. 得出结论 & 新问题展现
好吧,有结果了,B1和B2两种继承方式的效果的确不同,具体体现在多态行为上(有关多态的介绍,你可以参见《今天你多态了吗?》一文)。B1是个可怜虫,它必须接受A对IC.M的实现,无法改变这种命运;然而B2就不同,它有权选择接受还是拒绝,当然,拒绝的条件是提供有自己特色的实现。
4. 探索新问题 & 解决新问题
那么,我们如何纠正这种非预期的多态行为呢?一个简单的回答就是把B1的声明改成跟B2的一样。但这样,所有继承于A的派生类都必须照做,没得商量!还有其他的办法吗?有的,请先看如下代码:
interfaceIC
{
voidM();
}
classA:IC
{
voidIC.M()
{
this.M();
}
publicvirtualvoidM()
{
Console.WriteLine("InclassA");
}
}
classB1:A
{
publicoverridevoidM()
{
Console.WriteLine("InclassB1");
}
}
classB2:A,IC
{
publicoverridevoidM()
{
Console.WriteLine("InclassB2");
}
}
classProgram
{
staticvoidMain()
{
ListIC>cs=newListIC>();
cs.Add(newA());
cs.Add(newB1());
cs.Add(newB2());
foreach(ICcincs)
c.M();
}
}
运行结果为:
-
In class A
-
In class B1
- In class B2
这样,多态的效果就如我们所愿了!当然,现在B2声明中的IC又显得有点多余了,但你可以轻松把它拿掉!另外,如果测试程序换成:
classProgram
{
staticvoidMain()
{
ListA>ace=newListA>();
ace.Add(newA());
ace.Add(newB1());
ace.Add(newB2());
foreach(Aainace)
a.M();
}
}
结果还是一样!
5. 是的,我说谎了。[New]
或许你已经注意到,在上面的整个过程中,我做了一个最大的假设,那就是我可以任我喜欢修改A的源代码!也因为这样,我可以轻松的纠正这些非预期的多态行为。但实际的情况是,我们不会每次都那么幸运。如果我们仅仅得到一个包含类A和接口IC的程序集呢?那么,我们就需要使用到接口的重新映射了。实际上,B2就是使用这种技巧。还是让我们来看看具体的情况:
-
接口IC的规格不变。
- 我们只知道类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:
classAX:A,IC
{
//这里使用new是声明其与基类的同名方法M没有任何瓜葛。
//使用virtual是为后代的继承打下铺垫。
publicnewvirtualvoidM()
{
Console.WriteLine("InclassAX");
}
voidIC.M()
{
this.M();
}
}
classB1:AX
{
publicoverridevoidM()
{
Console.WriteLine("InclassB1");
}
}
classB2:AX
{
publicoverridevoidM()
{
Console.WriteLine("InclassB2");
}
}
好吧,然我们来看看测试程序:
相关推荐
我收集的C#基类,比较全.我收集的C#基类,比较全. 包含有: ASP.NET类库 C#读取AD域里用户名或组 Common类库 DotNet基础类大全 SqlHelper基类 字符串加密
c#数据操作基类如何将Sql Server 表的结构导出到Word或Excel
C#基类集合
c# xml 操作 基类
非常实用的ASP.NET类库、C#读取AD域里用户名或组、Common类库、DotNet基础类大全、SqlHelper基类、字符串加密
c++多重继承与虚基类的ppt 讲细致 易懂
C#基类库大全,包含Chart图形、FTP操作类、Json辅助类、PDF辅助类、XML操作类、弹出消息类、导出Excel、汉字转拼音、条形码、上传下载、验证码、邮件、正则表达式等一系列C#代码
C# 数据库操作基类 包括存储过程和事务处理
javax.servlet.Servlet接口 servlet接口中的方法 javax.servlet.http.HttpServlet类(HTTP版本)
c# 使用串口基类源码 可以选择端口彼特率
C#基类库大全(最全面的)
C#基类,里面包含各种辅助工具类,减少代码开发
1, 设计一个人员基类person类,包括描述基本信息的数据成员,提供基本操作的函数成员以及析构函数和不同形式的构造函数
主要介绍了C++编程中基类与基类的继承的相关知识,包括多个基类继承与虚拟基类等重要知识,需要的朋友可以参考下
主要介绍了C#子类对基类方法的继承、重写与隐藏的相关资料,文中通过示例代码介绍的非常详细,对大家的学习或者使用C#具有一定的参考学习价值,需要的朋友们下面来一起学习学习吧
C#开发一些常用的处理类库,例如:验证码、图表、邮件、正则、导出功能、上传下载功能等
分享一下 使用EF时,对增删改查基本操作的封装代码 ef重构基类;抛砖引玉,仅供参考; ef增删改差扩展方法!
从bicycle和motorcar派生出motorcycle,观察虚基类对继承的影响。 定义一个motorcycle的对象,分别调用run()和stop(),观察构造/析构函数的调用情况。 注意:构造函数和析构函数中均为cout语句,说明哪个构造/析构...
在各类中分别增加以下成员: base1中增加私有数据成员int b1 base2中增加私有数据成员int b2 level1中增加私有数据成员int l1 level2中增加私有数据成员int l2 toplevel中增加私有数据成员int t
我收集的C#基类,比较全.我收集的C#基类,比较全.