关于接口继承与覆盖基接口成员的讨论

发这篇帖子的起因源于一位朋友的发问:http://community.csdn.net/Expert/TopicView3.asp?id=5118454

该帖的回复中我没能给出有说服力的解答,在他的追问下,我被迫重新思考了他的提问,感觉我原先理解错了问题的意思,回答的似是而非,因此有必要在这里重新说明一下。

该朋友的问题如下:
interface IInterfaceA
{
void DoSomething();
}

interface IInterfaceB:IInterfaceA
{
new void DoSomething();//隐藏基接口的成员
}

//这两个接口有什么不一样吗?好像IInterfaceB隐藏IInterfaceA的成员DoSomething也没什么作用啊.
//能举个简单的例子(代码)帮我理解一下吗。谢谢。


他问的其实是这种应用的意义,换句话说,有必要一定这样做么?在哪里才会需要这种应用?
如果没有过实际的应用确实很难臆想有这种需求的场景,于是我在VS的随机文档中找到了这样一个例子。它原本是用来说明隐式接口实现与显式接口实现的区别的。
-----------------------------------------------------------------------------------------
显 式接口实现还允许程序员实现具有相同成员名称的两个接口,并为每个接口成员各提供一个实现。本示例同时以公制单位和英制单位显示框的尺寸。Box类实现 IEnglishDimensions 和 IMetricDimensions 两个接口,它们表示不同的度量系统。两个接口有相同的成员名 Length 和 Width。

示例
// Declare the English units interface:
interface IEnglishDimensions
{
float Length();
float Width();
}

// Declare the metric units interface:
interface IMetricDimensions
{
float Length();
float Width();
}

// Declare the Box class that implements the two interfaces:
// IEnglishDimensions and IMetricDimensions:
class Box : IEnglishDimensions, IMetricDimensions
{
float lengthInches;
float widthInches;

public Box(float length, float width)
{
lengthInches = length;
widthInches = width;
}

// Explicitly implement the members of IEnglishDimensions:
float IEnglishDimensions.Length()
{
return lengthInches;
}

float IEnglishDimensions.Width()
{
return widthInches;
}

// Explicitly implement the members of IMetricDimensions:
float IMetricDimensions.Length()
{
return lengthInches * 2.54f;
}

float IMetricDimensions.Width()
{
return widthInches * 2.54f;
}

static void Main()
{
// Declare a class instance box1:
Box box1 = new Box(30.0f, 20.0f);

// Declare an instance of the English units interface:
IEnglishDimensions eDimensions = (IEnglishDimensions)box1;

// Declare an instance of the metric units interface:
IMetricDimensions mDimensions = (IMetricDimensions)box1;

// Print dimensions in English units:
System.Console.WriteLine("Length(in): {0}", eDimensions.Length());
System.Console.WriteLine("Width (in): {0}", eDimensions.Width());

// Print dimensions in metric units:
System.Console.WriteLine("Length(cm): {0}", mDimensions.Length());
System.Console.WriteLine("Width (cm): {0}", mDimensions.Width());
}
}

输出

Length(in): 30
Width (in): 20
Length(cm): 76.2
Width (cm): 50.8

可靠编程
如果希望默认度量采用英制单位,请正常实现 Length 和 Width 这两个方法,并从 IMetricDimensions 接口显式实现 Length 和 Width 方法:

// Normal implementation:
public float Length()
{
return lengthInches;
}
public float Width()
{
return widthInches;
}

// Explicit implementation:
float IMetricDimensions.Length()
{
return lengthInches * 2.54f;
}
float IMetricDimensions.Width()
{
return widthInches * 2.54f;
}

这种情况下,可以从类实例访问英制单位,而从接口实例访问公制单位:

public static void Test()
{
Box box1 = new Box(30.0f, 20.0f);
IMetricDimensions mDimensions = (IMetricDimensions)box1;

System.Console.WriteLine("Length(in): {0}", box1.Length());
System.Console.WriteLine("Width (in): {0}", box1.Width());
System.Console.WriteLine("Length(cm): {0}", mDimensions.Length());
System.Console.WriteLine("Width (cm): {0}", mDimensions.Width());
}
-----------------------------------------------------------------------------------------
我对其中的接口定义进行了改写:
interface IEnglishDimensions
{
float Length();
float Width();
}
interface IMetricDimensions : IEnglishDimensions
{
new float Length();
new float Width();
}

在那位朋友提问的例子里,子接口中是否用new覆盖(注意我这里用词"覆盖",而非"隐藏"。)基接口的成员得到的效果都是一样的。但在我修改了接口定义后的MS示例里,new关键字就显示出了它的意义。如果我这样修改接口定义:
interface IEnglishDimensions
{
float Length();
float Width();
}
interface IMetricDimensions : IEnglishDimensions
{
//new float Length();
//new float Width();
}
那么这个示例是无法通过编译的,它会提示你

显式接口声明中的“IMetricDimensions.Length”不是接口成员
显式接口声明中的“IMetricDimensions.Width”不是接口成员

可通过编译的做法是在Class Box中显示实现IEnglishDimensions,而隐式实现IMetricDimensions。但那样我们会发现打印出的结果是这样的:

输出

Length(in): 30
Width (in): 20
Length(cm): 30
Width (cm): 20

现在接口继承又同时提供new关键字覆盖基接口成员的好处就显而易见了。

我保证了IMetricDimensions的接口定义与IMetricDimensions的严格一致。我的Box同时支持了两种计量制度,它们在成员定义上是严格相同的,只是进制不同。
我 也可以像MS 原例中那样书写,完全定义成两个不相关的接口,但如果我要为IEnglishDimensions增加任何成员的时候,我就必须不得同时去修改 IMetricDimensions的定义。如果我忘记了,或者在此出现了什么疏露,那么我的Box在应用IMetricDimensions这种公制计 量制度时就不能支持新增的成员。

以上乃 [缘木渔人 个人之见] 。

感兴趣的朋友也可以看看这篇文章http://www.miisoft.com/article/2/28/2006/200607258088.html,它是谈论new关键字在C#的多态中的应用的,讲的是基类与接口混合继承的情况。

感谢11Z大哥在我思考这个问题中给以启发并提供参考文章。
也感谢sh_city (高)朋友的发问,让我注意并深刻思考了这个问题。

----------------------------------------------------------------------------------------
另外,和11Z大哥讨论这个问题的时候,11Z老大认为C#做此设计的初衷是为了方便版本的管理与控制。昨天我还不甚理解,今天也在MSDN的文章中找到了证据。下面将引用原文。

原文是讨论类继承的情况的,但其实转换到接口中来也是一样。
---------------------------------------------------------------------------------------
C# 语言经过专门设计,以便不同库中的基类与派生类之间的版本控制可以不断向前发展,同时保持向后兼容。这具有多方面的意义。例如,这意味着在基类中引入与派 生类中的某个成员具有相同名称的新成员在 C# 中是完全支持的,不会导致意外行为。它还意味着类必须显式声明某方法是要重写一个继承方法,还是一个仅隐藏具有类似名称的继承方法的新方法。

C# 允许派生类包含与基类方法名称相同的方法。

基类方法必须定义为 virtual。

如果派生类中的方法前面没有 new 或 override 关键字,则编译器将发出警告,该方法将有如存在 new 关键字一样执行操作。

如果派生类中的方法前面带有 new 关键字,则该方法被定义为独立于基类中的方法。

如果派生类中的方法前面带有 override 关键字,则派生类的对象将调用该方法,而不调用基类方法。

可以从派生类中使用 base 关键字调用基类方法。

override、virtual 和 new 关键字还可以用于属性、索引器和事件中。

默 认情况下,C# 方法不是虚方法 -- 如果将一种方法声明为虚方法,则继承该方法的任何类都可以实现其自己的版本。若要使方法成为虚方法,必须在基类的方法声明中使用 virtual 修饰符。然后,派生类可以使用 override 关键字重写基虚方法,或使用 new 关键字隐藏基类中的虚方法。如果 override 关键字和 new 关键字均未指定,编译器将发出警告,并且派生类中的方法将隐藏基类中的方法。有关更多信息,请参见编译器警告 CS0108。

为了在实践中演示上述情况,我们暂时假定公司 A 创建了一个名为 GraphicsClass 的类,您的程序使用该类。GraphicsClass 类似如下:

C# 复制代码
class GraphicsClass
{
public virtual void DrawLine() { }
public virtual void DrawPoint() { }
}



您的公司使用此类,并且您在添加新方法时将其用来派生自己的类:

C# 复制代码
class YourDerivedGraphicsClass : GraphicsClass
{
public void DrawRectangle() { }
}



您在应用程序的使用过程中没有遇到任何问题,直到公司 A 发布了 GraphicsClass 的新版本,该新版本类似如下:

C# 复制代码
class GraphicsClass
{
public virtual void DrawLine() { }
public virtual void DrawPoint() { }
public virtual void DrawRectangle() { }
}



现 在,GraphicsClass 的新版本中包含了一个称为 DrawRectangle 的方法。最初,一切正常。新版本仍与旧版本二进制兼容 -- 即使在计算机系统中安装新类,部署的所有软件仍将继续工作。在您的派生类中,对方法 DrawRectangle 的任何现有调用将继续引用您的版本。

但是,一旦使用 GraphicsClass 的新版本重新编译应用程序,您将收到来自编译器的警告。有关更多信息,请参见编译器警告 CS0108。

此警告提示您需要考虑您的 DrawRectangle 方法在应用程序中的工作方式。

如果想用您的方法重写新的基类方法,请使用 override 关键字,如下所示:

C# 复制代码
class YourDerivedGraphicsClass : GraphicsClass
{
public override void DrawRectangle() { }
}



override 关键字可确保派生自 YourDerivedGraphicsClass 的任何对象都将使用 DrawRectangle 的派生类版本。派生自 YourDerivedGraphicsClass 的对象仍可以使用 base 关键字访问 DrawRectangle 的基类版本,如下所示:

C# 复制代码
base.DrawRectangle();



如 果不想用您的方法重写新的基类方法,则应注意下面的事项。为避免在两种方法之间引起混淆,可以重命名您的方法。重命名方法可能很耗时且容易出错,而且在某 些情况下并不实用。但是,如果您的项目相对较小,则可以使用 Visual Studio 的重构选项来重命名方法。有关更多信息,请参见重构类和类型。

或者,也可以通过在派生类定义中使用关键字 new 来防止出现该警告,如下所示:

C# 复制代码
class YourDerivedGraphicsClass : GraphicsClass
{
public new void DrawRectangle() { }
}



使用 new 关键字告诉编译器您的定义将隐藏基类中包含的定义。这是默认行为。

重写和方法选择
当在类中指定方法时,如果有多个方法与调用兼容(例如,存在两种同名的方法,并且其参数与传递的参数兼容),则 C# 编译器将选择最佳方法进行调用。下面的方法将是兼容的:

C# 复制代码
public class Derived : Base
{
public override void DoWork(int param) { }
public void DoWork(double param) { }
}



在 Derived 的一个实例中调用 DoWork 时,C# 编译器将首先尝试使该调用与最初在 Derived 上声明的 DoWork 版本兼容。重写方法不被视为是在类上进行声明的,而是在基类上声明的方法的新实现。仅当 C# 编译器无法将方法调用与 Derived 上的原始方法匹配时,它才尝试将该调用与具有相同名称和兼容参数的重写方法匹配。例如:

C# 复制代码
int val = 5;
Derived d = new Derived();
d.DoWork(val); // Calls DoWork(double).



由 于变量 val 可以隐式转换为 double 类型,因此 C# 编译器将调用 DoWork(double),而不是 DoWork(int)。有两种方法可以避免此情况。首先,避免将新方法声明为与虚方法同名。其次,可以通过将 Derived 的实例强制转换为 Base 来使 C# 编译器搜索基类方法列表,从而使其调用虚方法。由于是虚方法,因此将调用 Derived 上的 DoWork(int) 的实现。例如:

C# 复制代码
((Base)d).DoWork(val); // Calls DoWork(int) on Derived.


qltouming(缘木渔人)
2006-11-02
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值