中文10.操作符重载
利用操作符重载机制,程序员可以创建让人感觉自然的好似简单类型(如int、long等等)的类。C#实现了一个C++操作符重载的限制版,它可以使诸如这样的精辟的例子—复数类操作符重载表现良好。
在C#中,操作符==是对象类的非虚的(操作符不可以为虚的)方法,它是按引用比较的。当你构建一个类时,你可以定义你自己的==操作符。如果你在集合中使用你的类,你应该实现IComparable接口。这个接口有一个叫CompareTo(object)方法,如果“this”大于、小于或等于这个object,它应该相应返回正数、负数或0。如果你希望用户能够用优雅的语法使用你的类,你可以选择定义<、<=、>=、>方法。数值类型(int、long等等)实现了IComparable接口。
下面是一个如何处理等于和比较操作的简单例子:
public class Score : IComparable
{
int value;
public Score (int score)
{
value = score;
}
public static bool operator == (Score x, Score y)
{
return x.value == y.value;
}
public static bool operator != (Score x, Score y)
{
return x.value != y.value;
}
public int CompareTo (object o)
{
return value - ((Score)o).value;
}
}
Score a = new Score (5);
Score b = new Score (5);
Object c = a;
Object d = b;
按引用比较a和b:
System.Console.WriteLine ((object)a == (object)b; // 结果为false
【译注:上句代码应该为:System.Console.WriteLine ((object)a == (object)b); // 结果为false】
比较a和b的值:
System.Console.WriteLine (a == b); // 结果为true
按引用比较c和d:
System.Console.WriteLine (c == d); // 结果为false
比较c和d的值:
System.Console.WriteLine (((IComparable)c).CompareTo (d) == 0); // 结果为true
你还可以向Score类添加<、<=、>=、>操作符。C#在编译期保证逻辑上要成对出现的操作符(!=和==、>和<、>=和<=)必须一起被定义。
11.多态
面向对象的语言使用虚方法表达多态。这就意味着派生类可以有和父类具有同样签名的方法,并且父类可以调用派生类的方法【译注:此处应该是对象(或对象引用、指向对象的指针)】。在Java中,缺省情况下方法就是虚的。在C#中,必须使用virtual关键字才能使方法被父类调用。
在C#中,还需要override关键字以指明一个方法将重载(或实现一个抽象方法)其父类的方法。
Class B //【译注:应为class B】
{
public virtual void foo () {}
}
Class D : B //【译注:应为class D : B】
{
public override void foo () {}
}
试图重载一个非虚的方法将会导致一个编译时错误,除非对该方法加上“new”关键字,以指明该方法意欲隐藏父类的方法。
Class N : D //【译注:应为class N : D】
{
public new void foo () {}
}
N n = new N ();
n.foo(); // 调用N的foo
((D)n).foo(); // 调用D的foo
((B)n).foo(); // 调用D的foo
和C++、Java相比,C#的override关键字使得阅读源代码时可以清晰地看出哪些方法是重载的。不过,使用虚方法有利有弊。第一个有利点是:避免使用虚方法轻微的提高了执行速度。第二点是可以清楚地知道哪些方法会被重载。【译注:从“不过”至此,这几句话显然不合逻辑,但原文就是如此:“However, requiring the use of the virtual method has its pros and cons. The first pro is that is the slightly increased execution speed from avoiding virtual methods. The second pro is to make clear what methods are intended to be overridden.”。我认为,若将被我标为斜体的method改为keyword的话,逻辑上会顺畅些。这样,第一句话就可认为是和Java比,因其方法缺省是虚的,第二句话主要就是和C++比,原因参见我后面的相关注释】。然而,利也可能是弊。和Java中缺省忽略final修饰符【译注:在Java中可利用final关键字,对方法上锁,相当于C#/C++中没有用virtual关键字修饰方法/成员函数的情况】以及C++中缺省忽略virtual修饰符相比,Java中缺省选项【译注:即虚的】使得你程序略微损失一些效率,而在C++中,它可能妨碍了扩展性,虽然这对基类的实现者来说,是不可预料的。
【译注:“而在C++中,它可能妨碍了扩展性”这句话或许该这么理解:
class ParentCls
{
public:
virtual void f();
};
/////////////////////////////////////////////////////////////////////////////
class ChildCls : public ParentCls
{
public:
/*virtual*/ void f();/* 此处不标明为virtual的也是virtual的,但是GrandChildCls并不知道(假定GrandChildCls看不到ParentCls),它不知道应该对该方法overload(当然C++中并overload关键字,它是Object Pascal的,这儿再插一句话,overload和override两词翻译都一直都很混乱,Borland官方中文简体手册上都翻译成“重载”)还是override,还是不能碰。即它不知道多态机制在此是否会发生作用。或许你会说,试试不就知道啦J */
};
class GrandChildCls : public ChildCls
{
public:
void f();
};
】
12.接口
C#中的接口和Java中的接口差不多,但是有更大的弹性。类可以随意地显式实现某个接口:
public interface ITeller
{
void Next ();
}
public interface IIterator
{
void Next ();
}
public class Clark : ITeller, IIterator
{
void ITeller.Next () {}
void IIterator.Next () {}
}
这给实现接口的类带来了两个好处。其一,一个类可以实现若干接口而不必担心命名冲突问题。其二,如果某方法对一般用户来说没有用的话,类能够隐藏该方法。显式实现的方法的调用,需把类【译注:应该是对象】造型转换为接口:
Clark clark = new Clark();
((ITeller)clark).Next();
13.版本处理
解决版本问题已成为.NET框架一个主要考虑。这些考虑的大多数都体现于组合体中。在C#中,可在同一个进程里运行同一个组合体的不同版本的能力是令人印象深刻的。
当代码的新版本(尤其是.NET库)被创建时,C#可以防止软件失败。C#语言参考里详细地描述了该问题。我用一个例子简明扼要地讲解如下:
在Java中,假定我们部署一个称为D的类,它是从一个通过VM发布的叫B的类派生下来的。类D有一个叫foo的方法,而它在B发布时,B还没有这个方法。后来,对类B做了个升级,现在B包括了一个叫foo的方法,新的VM现在安装在使用类D的机器上了。现在,使用D的软件可能会发生故障了,因为类B的新实现可能会导致一个对D的虚函数调用,这就执行了一个类B始料未及的动作。【译注:因Java中方法缺省是虚的】在C#中,类D的foo方法应该声明为不用override修饰符的(这个真正表达了程序员的意愿),因此,运行时知道让类D的foo方法隐藏类B的foo方法,而不是重载它。
引用C#参考手册的一句有意思的话“C#处理版本问题是通过需要开发人员明确他们的意图来实现的”。尽管使用override是一个表达意图的办法,但编译器也能自动生成—通过在编译时检查方法是否在执行(而不是声明)一个重载。这就意味着,你仍然能够拥有象Java一样的语言(Java不用virtual和override关键字),并且仍然能够正确处理版本问题。
参见字段修饰符部分。
14.参数修饰符
(1)ref参数修饰符
C#(和Java相比)可以让你按引用传递参数。描述这一点的最明显的例子是通用交换方法。不象C++,不但是声明时,调用时也要加上ref指示符:【译注:不要误会这句话,C++中当然是没有ref关键字】
public class Test
{
public static void Main ()
{
int a = 1;
int b = 2;
swap (ref a, ref b);
}
public static void swap (ref int a, ref int b)
{
int temp = a;
a = b;
b = temp;
}
}
(2)out参数修饰符
out关键字是对ref参数修饰符的自然补充。Ref修饰符要求参数在传入方法之前必须被赋值。而out修饰符则明确当方法返回时需显式给参数赋值,。
(3)params参数修饰符
params修饰符可被加在方法的最后的参数上,方法将接受任意数量的指定类型的参数【译注:在一个方法声明中,只允许一个params性质的参数】。例如:
public class Test
{
public static void Main ()
{
Console.WriteLine (add (1, 2, 3, 4).ToString());
}
public static int add (params int[] array)
{
int sum = 0;
foreach (int i in array)
sum += i;
return sum;
}
}
【作者注:学习Java时一个非常令人诧异的事是发现Java不能按引用传递参数,尽管不久以后,你很少会再想要这个功能,并且写代码时也不需要它了。当我第一次阅读C#规范的时候,我常想,“他们干吗把加上这个功能,没有它我也能写代码”。经过反省以后,我意识到这其实并不是说明某些功能是否有用的问题,更多是说明了没有它你就另需别的条件才能实现的问题。
A Comparative Overview of C#中文版(三)
80酷酷网 80kuku.com