中文
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#規範的時候,我常想,“他們幹嗎把加上這個功能,沒有它我也能寫代碼”。經過反省以後,我意識到這其實並不是說明某些功能是否有用的問題,更多是說明了沒有它你就另需別的條件才能實現的問題。