原文地址:C# Operator Overloading - The Crazy Side - Part 2

      欢迎回到疯子的世界,如果想写出易理解的代码,这里的很多事情你都不会去做的!在这个特殊的例子中,我将谈谈当你将operator 重载和继承,多态混合使用的时候,在C#里面会有什么发生。如果你想复习下C# operator 重载,你可以去读下这个教程的第一部分,在Overloading Part 1. 现在,废话少说,让我们开始吧!

 开始这段之前,我们首先需要一些为多态而产生的不同代码。下面是三个包含一些重载operator操作符的类:

Code

    这里我们定义了三个类:BaseClass, ExtendedClassA,  ExtendedClassB。相信你应该已经从他们的名字中猜到了,ExtendedClassA  ExtendedClassB 继承类 BaseClass

   下面是第一段我们要用来测试这些操作符重载的代码片段:

Code

    代码写好了,这里我们创建了一些测试类的实例,我们主要关心的是变量resultHolder的输出,结果如下:

Addition in: BaseClass
Addition 
in: ExtendedClassA
Addition 
in: BaseClass
Subtraction 
in: ExtendedClassA
Division 
in: BaseClass
Addition 
in: BaseClass
Addition 
in: BaseClass
Addition 
in: BaseClass

      现在让我们通过结果一行行的分析是怎么回事。首先,行"resultholder = baseClass + baseClass"结果是:BaseClass.这个没问题,两个BaseClass相加会调用在BaseClass里面的加方法。下一行, resultholder = extA + extA 输出"ExtendedClassA "是一样的道理。它表明两个ExtendedClassA实例相加会调用ExtendedClassA中定义的加方法。

     从第三行起,开始有点意思了,resultholder = baseClass + extA结果为:BaseClass。在ExtendedClassA BaseClass 里面都没有这个操作的重载方法(BaseClass arg1, ExtendedClassA arg2),你可能会认为这个方法会fail掉,但编译器将ExtendedClassA 实例当做BaseClass实例来处理,最后调用BaseClass里面的Add方法。

    第四行, resultholder = extA - extA输出ExtendedClassA,这表明即使ExtendedClassA 并没有可匹配的相关操作方法,它仍然会尝试对参数进行向下转型从而使得找到一个符合签名的方法。在这个例子中它找到且调用了ExtendedClassA中的subtract方法。这个方法传进ExtendedClassABaseClass实例作为参数。

注:在第四行,C#这样子做有时候会产生潜在的歧义。我们马上来看下下面的代码:

Code

     如果你想对两个MessedUpClass实例进行减操作,编译器会报错:

MessedUpClass a = new MessedUpClass();
= a + a;
//Error: The call is ambiguous between the following methods or properties: 'MessedUpClass.operator -(MessedUpClass, BaseClass)' and 'MessedUpClass.operator -(BaseClass, MessedUpClass)'

    什么意思呢?为了使方法的调用符合签名,编译器如何知道应当对哪个对象进行向下转换操作呢?

继续回到测试代码中来。第五行,resultholder = extA / extA显示除法操作结果为:BaseClass。这个是第四行的扩展。这里并没有符合条件的重载方法,但通过向下转型操作,它调用了基类的除方法。

    第六行中,我们对ExtendedClassA 实例和ExtendedClassB实例进行Add操作:resultholder = extA + extB,结果为:BaseClass。这是另外一个向下转型的例子了。两个扩展类中都没有对这样的参数进行Add操作的重载方法。但编译器能通过向下转型来调用BaseClass中的Add方法。同样,就像第四行的变化一样,如果你也疯狂尝试这种类型的重载方法,编译器很可能抛出模棱两可的错误来。

    下面到第七行:resultholder = extA + extAinBase结果是:BaseClass。这里我们产生了一个以BaseClass 变量形式存在的ExtendedClassA 实例。从这里我们可以看到操作符重载实际上并非多态的。如果它是多态的,那么就应该是ExtendedClassA 中的Add重载操作被调用,因为在运行时虚拟机知道extAinBase 实际上是ExtendedClassA的实例。但因为操作符重载是静态的(i.e.,编译时),编译器只知道extAinBase BaseClass,除了调用BaseClass中的Add方法,其别无选择。

有时候,你应当非常庆幸操作符重载不是动态(i.e.,运行时)操作。很多时候,操作符重载本身就够让人疑惑的,使其动态将会使调用它的代码比标准的重载可读性更差。

    上次我们通过第八行"extAinBase "自加运算(resultholder = extAinBase + extAinBase),且期望它会调用BaseClass里面的Add重载方法。毫无疑问,它遵从我们已经测试过的第七行结果。

    这里给出到目前为止另外一个产生错误的代码来复现重载不是动态的事实:

resultholder = extAinBase * extAinBase;
//Error: Operator '*' cannot be applied to operands of type 'BaseClass' and 'BaseClass'

    在ExtendedClassA是有乘的重载操作符方法的,但如你所见,它完全忽略这个方法,还是同样的道理,编译器知道的所有东西就是我们想要对两个BaseClass的实例做乘操作(它并没有乘操作符的方法)。

    本文主要探讨了关于C#操作符重载如何和多态和继承一起作用的。最后要注意的是操作符重载方法是不能在接口中声明的(再一次强调,由于操作符重载是在编译时就决定的,如果它指向的是一个接口,编译器应当如何映射该重载到实际的操作符方法中去呢?)

 

希望你能享受这段疯狂之旅!

posted on 2009-05-09 23:10  jujusharp  阅读(980)  评论(0编辑  收藏  举报