C#(C-Sharp)是Microsoft的新編程語(yǔ)言,被譽(yù)為“C/C++家族中第一種面向組件的語(yǔ)言”。然而,不管它自己宣稱(chēng)的是什么,許多人認(rèn)為C#更像是Java的一種克隆,或者是Microsoft用來(lái)替代Java的產(chǎn)品。事實(shí)是否是這樣的呢?
本文的比較結(jié)果表明,C#不止是Java的同胞那么簡(jiǎn)單。如果你是一個(gè)Java開(kāi)發(fā)者,想要學(xué)習(xí)C#或者了解更多有關(guān)C#的知識(shí),那么本文就是你必須把最初10分鐘投入于其中的所在。
一、C#、C++和Java
C#的語(yǔ)言規(guī)范由Microsoft的Anders Hejlsberg與Scott Wiltamuth編寫(xiě)。在當(dāng)前Microsoft天花亂墜的宣傳中,對(duì)C#和C++、Java作一番比較總是很有趣的??紤]到當(dāng)前IT媒體的輿論傾向,如果你早就知道C#更接近Java而不是C++,事情也不值得大驚小怪。對(duì)于剛剛加入這場(chǎng)討論的讀者,下面的表1讓你自己作出判斷。顯然,結(jié)論應(yīng)該是:Java和C#雖然不是孿生子,但C#最主要的特色卻更接近Java而不是C++。
表1:比較C#、C++和Java最重要的功能
功能 C# C++ Java
繼承 允許繼承單個(gè)類(lèi),允許實(shí)現(xiàn)多個(gè)接口 允許從多個(gè)類(lèi)繼承 允許繼承單個(gè)類(lèi),允許實(shí)現(xiàn)多個(gè)接口
接口實(shí)現(xiàn) 通過(guò)“interface”關(guān)鍵詞 通過(guò)抽象類(lèi) 通過(guò)“interface”關(guān)鍵詞
內(nèi)存管理 由運(yùn)行時(shí)環(huán)境管理,使用垃圾收集器 需要手工管理 由運(yùn)行時(shí)環(huán)境管理,使用垃圾收集器
指針 支持,但只在很少使用的非安全模式下才支持。通常以引用取代指針 支持,一種很常用的功能。 完全不支持。代之以引用。
源代碼編譯后的形式 .NET中間語(yǔ)言(IL) 可執(zhí)行代碼 字節(jié)碼
單一的公共基類(lèi) 是 否 是
異常處理 異常處理 返回錯(cuò)誤 異常處理。
了解表1總結(jié)的重要語(yǔ)言功能之后,請(qǐng)繼續(xù)往下閱讀,了解C#和Java的一些重要區(qū)別。
二、語(yǔ)言規(guī)范的比較
2.1、簡(jiǎn)單數(shù)據(jù)類(lèi)型
簡(jiǎn)單數(shù)據(jù)類(lèi)型(Primitive)在C#中稱(chēng)為值類(lèi)型,C#預(yù)定義的簡(jiǎn)單數(shù)據(jù)類(lèi)型比Java多。例如,C#有unit,即無(wú)符號(hào)整數(shù)。表2列出了所有C#的預(yù)定義數(shù)據(jù)類(lèi)型:
表2:C#中的值類(lèi)型
類(lèi)型 說(shuō)明
object 所有類(lèi)型的最終極的基類(lèi)
string 字符串類(lèi)型;字符串是一個(gè)Unicode字符的序列
sbyte 8位帶符號(hào)整數(shù)
short 16位帶符號(hào)整數(shù)
int 32位帶符號(hào)整數(shù)
long 64位帶符號(hào)整數(shù)
byte 8位無(wú)符號(hào)整數(shù)
ushort 16位無(wú)符號(hào)整數(shù)
uint 32位無(wú)符號(hào)整數(shù)
ulong 64位無(wú)符號(hào)整數(shù)
float 單精度浮點(diǎn)數(shù)類(lèi)型
double 雙精度浮點(diǎn)數(shù)類(lèi)型
bool 布爾類(lèi)型;bool值或者是true,或者是false
char 字符類(lèi)型;一個(gè)char值即是一個(gè)Unicode字符
decimal 有28位有效數(shù)字的高精度小數(shù)類(lèi)型
2.2、常量
忘掉Java中的static final修飾符。在C#中,常量可以用const關(guān)鍵詞聲明。
public const int x = 55;
此外,C#的設(shè)計(jì)者還增加了readonly關(guān)鍵詞。如果編譯器編譯時(shí)未能確定常量值,你可以使用readonly關(guān)鍵詞。readonly域只能通過(guò)初始化器或類(lèi)的構(gòu)造函數(shù)設(shè)置。
2.3、公用類(lèi)的入口點(diǎn)
在Java中,公用類(lèi)的入口點(diǎn)是一個(gè)名為main的公用靜態(tài)方法。main方法的參數(shù)是String對(duì)象數(shù)組,它沒(méi)有返回值。在C#中,main方法變成了公用靜態(tài)方法Main(大寫(xiě)的M),Main方法的參數(shù)也是一個(gè)String對(duì)象數(shù)組,而且也沒(méi)有返回值,如下面的原型聲明所示:
public static void Main(String[] args)
但是,C#的Main方法不局限于此。如果不向Main方法傳遞任何參數(shù),你可以使用上述Main方法的一個(gè)重載版本,即不帶參數(shù)列表的版本。也就是說(shuō),下面的Main方法也是一個(gè)合法的入口點(diǎn):
public static void Main()
另外,如果你認(rèn)為有必要的話(huà),Main方法還可以返回一個(gè)int。例如,下面代碼中的Main方法返回1:
using System;
public class Hello {
public static int Main() {
Console.WriteLine("Done");
return 1;
}
}
與此相對(duì),在Java中重載main方法是不合法的。
2.4、switch語(yǔ)句
在Java中,switch語(yǔ)句只能處理整數(shù)。但C#中的switch語(yǔ)句不同,它還能夠處理字符變量。請(qǐng)考慮下面用switch語(yǔ)句處理字符串變量的C#代碼:
using System;
public class Hello {
public static void Main(String[] args) {
switch (args[0]) {
case "老板":
Console.WriteLine("早上好!我們隨時(shí)準(zhǔn)備為您效勞!");
break;
case "雇員":
Console.WriteLine("早上好!你可以開(kāi)始工作了!");
break;
default:
Console.WriteLine("早上好!祝你好運(yùn)!");
break;
}
}
}
與Java中的switch不同,C#的switch語(yǔ)句要求每一個(gè)case塊或者在塊的末尾提供一個(gè)break語(yǔ)句,或者用goto轉(zhuǎn)到switch內(nèi)的其他case標(biāo)簽。
2.5、foreach語(yǔ)句
foreach語(yǔ)句枚舉集合中的各個(gè)元素,為集合中的每一個(gè)元素執(zhí)行一次代碼塊。請(qǐng)參見(jiàn)下面的例子。
using System;
public class Hello {
public static void Main(String[] args) {
foreach (String arg in args)
Console.WriteLine(arg);
}
}
如果在運(yùn)行這個(gè)執(zhí)行文件的時(shí)候指定了參數(shù),比如“Hello Peter Kevin Richard”,則程序的輸出將是下面幾行文字:
Peter
Kevin
Richard
2.6、C#沒(méi)有>>>移位操作符
C#支持uint和ulong之類(lèi)的無(wú)符號(hào)變量類(lèi)型。因此,在C#中,右移操作符(即“>>”)對(duì)于無(wú)符號(hào)變量類(lèi)型和帶符號(hào)變量類(lèi)型(比如int和long)的處理方式不同。右移uint和ulong丟棄低位并把空出的高位設(shè)置為零;但對(duì)于int和long類(lèi)型的變量,“>>”操作符丟棄低位,同時(shí),只有當(dāng)變量值是正數(shù)時(shí),“>>”才把空出的高位設(shè)置成零;如果“>>”操作的是一個(gè)負(fù)數(shù),空出的高位被設(shè)置成為1。
Java中不存在無(wú)符號(hào)的變量類(lèi)型。因此,我們用“>>>”操作符在右移時(shí)引入負(fù)號(hào)位;否則,使用“>>”操作符。
2.7、goto關(guān)鍵詞
Java不用goto關(guān)鍵詞。在C#中,goto允許你轉(zhuǎn)到指定的標(biāo)簽。不過(guò),C#以特別謹(jǐn)慎的態(tài)度對(duì)待goto,比如它不允許goto轉(zhuǎn)入到語(yǔ)句塊的內(nèi)部。在Java中,你可以用帶標(biāo)簽的語(yǔ)句加上break或continue取代C#中的goto。
2.8、聲明數(shù)組
在Java中,數(shù)組的聲明方法非常靈活,實(shí)際上有許多種聲明方法都屬于合法的方法。例如,下面的幾行代碼是等價(jià)的:
int[] x = { 0, 1, 2, 3 };
int x[] = { 0, 1, 2, 3 };
但在C#中,只有第一行代碼合法,[]不能放到變量名字之后。
2.9、包
在C#中,包(Package)被稱(chēng)為名稱(chēng)空間。把名稱(chēng)空間引入C#程序的關(guān)鍵詞是“using”。例如,“using System;”這個(gè)語(yǔ)句引入了System名稱(chēng)空間。
然而,與Java不同的是,C#允許為名稱(chēng)空間或者名稱(chēng)空間中的類(lèi)指定別名:
using TheConsole = System.Console;
public class Hello {
public static void Main() {
TheConsole.WriteLine("使用別名");
}
}
雖然從概念上看,Java的包類(lèi)似于.NET的名稱(chēng)空間。然而,兩者的實(shí)現(xiàn)方式不同。在Java中,包的名字同時(shí)也是實(shí)際存在的實(shí)體,它決定了放置.java文件的目錄結(jié)構(gòu)。在C#校錮淼陌吐嘸拿浦涫峭耆擲氳?,也就是藫?dān)瓶占淶拿植換岫暈錮淼拇虬絞講魏斡跋臁T贑#中,每一個(gè)源代碼文件可以從屬于多個(gè)名稱(chēng)空間,而且它可以容納多個(gè)公共類(lèi)。
.NET中包的實(shí)體稱(chēng)為程序集(Assembly)。每一個(gè)程序集包含一個(gè)manifest結(jié)構(gòu)。manifest列舉程序集所包含的文件,控制哪些類(lèi)型和資源被顯露到程序集之外,并把對(duì)這些類(lèi)型和資源的引用映射到包含這些類(lèi)型與資源的文件。程序集是自包含的,一個(gè)程序集可以放置到單一的文件之內(nèi),也可以分割成多個(gè)文件。.NET的這種封裝機(jī)制解決了DLL文件所面臨的問(wèn)題,即臭名昭著的DLL Hell問(wèn)題。
2.10、默認(rèn)包
在Java中,java.lang包是默認(rèn)的包,它無(wú)需顯式導(dǎo)入就已經(jīng)自動(dòng)包含。例如,要把一些文本輸出到控制臺(tái),你可以使用下面的代碼:
System.out.println("Hello world from Java");
C#中不存在默認(rèn)的包。如果要向控制臺(tái)輸出文本,你使用System名稱(chēng)空間Console對(duì)象的WriteLine方法。但是,你必須顯式導(dǎo)入所有的類(lèi)。代碼如下:
using System;
public class Hello {
public static void Main() {
Console.WriteLine("Hello world from C#");
}
}
2.11、面向?qū)ο?br> Java和C#都是完全面向?qū)ο蟮恼Z(yǔ)言。在面向?qū)ο缶幊痰娜笤瓌t方面,這兩種語(yǔ)言接近得不能再接近。
繼承:這兩種語(yǔ)言都支持類(lèi)的單一繼承,但類(lèi)可以實(shí)現(xiàn)多個(gè)接口。所有類(lèi)都從一個(gè)公共的基類(lèi)繼承。
封裝與可見(jiàn)性:無(wú)論是在Java還是C#中,你都可以決定類(lèi)成員是否可見(jiàn)。除了C#的internal訪(fǎng)問(wèn)修飾符之外,兩者的可見(jiàn)性機(jī)制非常相似。
多態(tài)性:Java和C#都支持某些形式的多態(tài)性機(jī)制,且兩者實(shí)現(xiàn)方法非常類(lèi)似。
2.12、可訪(fǎng)問(wèn)性
類(lèi)的每個(gè)成員都有特定類(lèi)型的可訪(fǎng)問(wèn)性。C#中的訪(fǎng)問(wèn)修飾符與Java中的基本對(duì)應(yīng),但多出了一個(gè)internal。簡(jiǎn)而言之,C#有5種類(lèi)型的可訪(fǎng)問(wèn)性,如下所示:
public:成員可以從任何代碼訪(fǎng)問(wèn)。
protected:成員只能從派生類(lèi)訪(fǎng)問(wèn)。
internal:成員只能從同一程序集的內(nèi)部訪(fǎng)問(wèn)。
protected internal:成員只能從同一程序集內(nèi)的派生類(lèi)訪(fǎng)問(wèn)。
private:成員只能在當(dāng)前類(lèi)的內(nèi)部訪(fǎng)問(wèn)。
2.13、派生類(lèi)
在Java中,我們用關(guān)鍵詞“extends”實(shí)現(xiàn)繼承。C#采用了C++的類(lèi)派生語(yǔ)法。例如,下面的代碼顯示了如何派生父類(lèi)Control從而創(chuàng)建出新類(lèi)Button:
public class Button: Control { . . }
2.14、最終類(lèi)
由于C#中不存在final關(guān)鍵詞,如果想要某個(gè)類(lèi)不再被派生,你可以使用sealed關(guān)鍵詞,如下例所示:
sealed class FinalClass { . . }
2.15、接口
接口這個(gè)概念在C#和Java中非常相似。接口的關(guān)鍵詞是interface,一個(gè)接口可以擴(kuò)展一個(gè)或者多個(gè)其他接口。按照慣例,接口的名字以大寫(xiě)字母“I”開(kāi)頭。下面的代碼是C#接口的一個(gè)例子,它與Java中的接口完全一樣:
interface IShape { void Draw(); }
擴(kuò)展接口的語(yǔ)法與擴(kuò)展類(lèi)的語(yǔ)法一樣。例如,下例的IRectangularShape接口擴(kuò)展IShape接口(即,從IShape接口派生出IRectangularShape接口)。
interface IRectangularShape: IShape { int GetWidth(); }
如果你從兩個(gè)或者兩個(gè)以上的接口派生,父接口的名字列表用逗號(hào)分隔,如下面的代碼所示:
interface INewInterface: IParent1, IParent2 { }
然而,與Java不同,C#中的接口不能包含域(Field)。
另外還要注意,在C#中,接口內(nèi)的所有方法默認(rèn)都是公用方法。在Java中,方法聲明可以帶有public修飾符(即使這并非必要),但在C#中,顯式為接口的方法指定public修飾符是非法的。例如,下面的C#接口將產(chǎn)生一個(gè)編譯錯(cuò)誤。
interface IShape { public void Draw(); }
2.16、is和as操作符
C#中的is操作符與Java中的instanceof操作符一樣,兩者都可以用來(lái)測(cè)試某個(gè)對(duì)象的實(shí)例是否屬于特定的類(lèi)型。在Java中沒(méi)有與C#中的as操作符等價(jià)的操作符。as操作符與is操作符非常相似,但它更富有“進(jìn)取心”:如果類(lèi)型正確的話(huà),as操作符會(huì)嘗試把被測(cè)試的對(duì)象引用轉(zhuǎn)換成目標(biāo)類(lèi)型;否則,它把變量引用設(shè)置成null。
為正確理解as操作符,首先請(qǐng)考慮下面這個(gè)例子中is操作符的運(yùn)用。這個(gè)例子包含一個(gè)IShape接口,以及兩個(gè)實(shí)現(xiàn)了IShape接口的類(lèi)Rectangle和Circle。
using System;
interface IShape {
void draw();
}
public class Rectangle: IShape {
public void draw() {
}
public int GetWidth() {
return 6;
}
}
public class Circle: IShape {
public void draw() {
}
public int GetRadius() {
return 5;
}
}
public class LetsDraw {
public static void Main(String[] args) {
IShape shape = null;
if (args[0] == "rectangle") {
shape = new Rectangle();
}
else if (args[0] == "circle") {
shape = new Circle();
}
if (shape is Rectangle) {
Rectangle rectangle = (Rectangle) shape;
Console.WriteLine("Width : " + rectangle.GetWidth());
}
if (shape is Circle) {
Circle circle = (Circle) shape;
Console.WriteLine("Radius : " + circle.GetRadius());
}
}
}
編譯好代碼之后,用戶(hù)可以輸入“rectangle”或者“circle”作為Main方法的參數(shù)。如果用戶(hù)輸入的是“circle”,則shape被實(shí)例化成為一個(gè)Circle類(lèi)型的對(duì)象;反之,如果用戶(hù)輸入的是“rectangle”,則shape被實(shí)例化成為Rectangle類(lèi)型的對(duì)象。隨后,程序用is操作符測(cè)試shape的變量類(lèi)型:如果shape是一個(gè)矩形,則shape被轉(zhuǎn)換成為Rectangle對(duì)象,我們調(diào)用它的GetWidth方法;如果shape是一個(gè)圓,則shape被轉(zhuǎn)換成為一個(gè)Circle對(duì)象,我們調(diào)用它的GetRadius方法。
如果使用as操作符,則上述代碼可以改成如下形式:
using System;
interface IShape {
void draw();
}
public class Rectangle: IShape {
public void draw() {
}
public int GetWidth() {
return 6;
}
}
public class Circle: IShape {
public void draw() {
}
public int GetRadius() {
return 5;
}
}
public class LetsDraw {
public static void Main(String[] args) {
IShape shape = null;
if (args[0] == "rectangle") {
shape = new Rectangle();
}
else if (args[0] == "circle") {
shape = new Circle();
}
Rectangle rectangle = shape as Rectangle;
if (rectangle != null) {
Console.WriteLine("Width : " + rectangle.GetWidth());
}
else {
Circle circle = shape as Circle;
if (circle != null)
Console.WriteLine("Radius : " + circle.GetRadius());
}
}
}
在上面代碼的粗體部分中,我們?cè)跊](méi)有測(cè)試shape對(duì)象類(lèi)型的情況下,就用as操作符把shape轉(zhuǎn)換成Rectangle類(lèi)型的對(duì)象。如果shape正好是一個(gè)Rectangle,則shape被轉(zhuǎn)換成為Rectangle類(lèi)型的對(duì)象并保存到rectangle變量,然后我們調(diào)用它的GetWidth方法。如果這種轉(zhuǎn)換失敗,則我們進(jìn)行第二次嘗試。這一次,shape被轉(zhuǎn)換成為Circle類(lèi)型的對(duì)象并保存到circle變量。如果shape確實(shí)是一個(gè)Circle對(duì)象,則circle現(xiàn)在引用了一個(gè)Circle對(duì)象,我們調(diào)用它的GetRadius方法。
2.17、庫(kù)
C#沒(méi)有自己的類(lèi)庫(kù)。但是,C#共享了.NET的類(lèi)庫(kù)。當(dāng)然,.NET類(lèi)庫(kù)也可以用于其他.NET語(yǔ)言,比如VB.NET或者JScript.NET。值得一提的是StringBuilder類(lèi),它是對(duì)String類(lèi)的補(bǔ)充。StringBuilder類(lèi)與Java的StringBuffer類(lèi)非常相似。
2.18、垃圾收集
C++已經(jīng)讓我們認(rèn)識(shí)到手工管理內(nèi)存是多么缺乏效率和浪費(fèi)時(shí)間。當(dāng)你在C++中創(chuàng)建了一個(gè)對(duì)象,你就必須手工地拆除這個(gè)對(duì)象。代碼越復(fù)雜,這個(gè)任務(wù)也越困難。Java用垃圾收集器來(lái)解決這個(gè)問(wèn)題,由垃圾收集器搜集不再使用的對(duì)象并釋放內(nèi)存。C#同樣采用了這種方法。應(yīng)該說(shuō),如果你也在開(kāi)發(fā)一種新的OOP語(yǔ)言,追隨這條道路是一種非常自然的選擇。C#仍舊保留了C++的內(nèi)存手工管理方法,它適合在速度極端重要的場(chǎng)合使用,而在Java中這是不允許的。
2.19、異常處理
如果你聽(tīng)說(shuō)C#使用與Java相似的異常處理機(jī)制,你不會(huì)為此而驚訝,對(duì)吧?在C#中,所有的異常都從一個(gè)名為Exception的類(lèi)派生(聽(tīng)起來(lái)很熟悉?)另外,正如在Java中一樣,你還有熟悉的try和catch語(yǔ)句。Exception類(lèi)屬于.NET System名稱(chēng)空間的一部分。
三、Java沒(méi)有的功能
C#出生在Java成熟之后,因此,C#擁有一些Java(目前)還沒(méi)有的絕妙功能也就不足為奇。
3.1、枚舉器
枚舉器即enum類(lèi)型(Enumerator,或稱(chēng)為計(jì)數(shù)器),它是一個(gè)相關(guān)常量的集合。精確地說(shuō),enum類(lèi)型聲明為一組相關(guān)的符號(hào)常量定義了一個(gè)類(lèi)型名字。例如,你可以創(chuàng)建一個(gè)名為Fruit(水果)的枚舉器,把它作為一個(gè)變量值的類(lèi)型使用,從而把變量可能的取值范圍限制為枚舉器中出現(xiàn)的值。
public class Demo {
public enum Fruit {
Apple, Banana, Cherry, Durian
}
public void Process(Fruit fruit) {
switch (fruit) {
case Fruit.Apple:
...
break;
case Fruit.Banana:
...
break;
case Fruit.Cherry:
...
break;
case Fruit.Durian:
...
break;
}
}
}
在上例的Process方法中,雖然你可以用int作為myVar變量的類(lèi)型,但是,使用枚舉器Fruit之后,變量的取值范圍限制到了Applet、Banana、Cherry和Durian這幾個(gè)值之內(nèi)。與int相比,enum的可讀性更好,自我說(shuō)明能力更強(qiáng)。
3.2、結(jié)構(gòu)
結(jié)構(gòu)(Struct)與類(lèi)很相似。然而,類(lèi)是作為一種引用類(lèi)型在堆中創(chuàng)建,而結(jié)構(gòu)是一種值類(lèi)型,它存儲(chǔ)在棧中或者是嵌入式的。因此,只要謹(jǐn)慎運(yùn)用,結(jié)構(gòu)要比類(lèi)快。結(jié)構(gòu)可以實(shí)現(xiàn)接口,可以象類(lèi)一樣擁有成員,但結(jié)構(gòu)不支持繼承。
然而,簡(jiǎn)單地用結(jié)構(gòu)來(lái)取代類(lèi)可能導(dǎo)致慘重?fù)p失。這是因?yàn)?,結(jié)構(gòu)是以值的方式傳遞,由于這種傳遞方式要把值復(fù)制到新的位置,所以傳遞一個(gè)“肥胖的”結(jié)構(gòu)需要較大的開(kāi)銷(xiāo)。而對(duì)于類(lèi),傳遞的時(shí)候只需傳遞它的引用。
下面是一個(gè)結(jié)構(gòu)的例子。注意它與類(lèi)非常相似,只要把單詞“struct”替換成“class”,你就得到了一個(gè)類(lèi)。
struct Point {
public int x, y;
public Point(int x, int y) {
this.x = x;
this.y = y;
}
}
3.3、屬性
C#類(lèi)除了可以擁有域(Field)之外,它還可以擁有屬性(Property)。屬性是一個(gè)與類(lèi)或?qū)ο箨P(guān)聯(lián)的命名的特征。屬性是域的一種自然擴(kuò)展――兩者都是有類(lèi)型、有名字的類(lèi)成員。然而,和域不同的是,屬性不表示存儲(chǔ)位置;相反,屬性擁有存取器(accessor),存取器定義了讀取或者寫(xiě)入屬性值時(shí)必須執(zhí)行的代碼。因此,屬性提供了一種把動(dòng)作和讀取、寫(xiě)入對(duì)象屬性值的操作關(guān)聯(lián)起來(lái)的機(jī)制,而且它們?cè)试S屬性值通過(guò)計(jì)算得到。
在C#中,屬性通過(guò)屬性聲明語(yǔ)法定義。屬性聲明語(yǔ)法的第一部分與域聲明很相似,第二部分包括一個(gè)set過(guò)程和/或一個(gè)get過(guò)程。例如,在下面的例子中,PropertyDemo類(lèi)定義了一個(gè)Prop屬性。
public class PropertyDemo {
private string prop;
public string Prop {
get {
return prop;
}
set {
prop = value;
}
}
}
如果屬性既允許讀取也允許寫(xiě)入,如PropertyDemo類(lèi)的Prop屬性,則它同時(shí)擁有g(shù)et和set存取過(guò)程。當(dāng)我們讀取屬性的值時(shí),get存取過(guò)程被調(diào)用;當(dāng)我們寫(xiě)入屬性值時(shí),set存取過(guò)程被調(diào)用。在set存取過(guò)程中,屬性的新值在一個(gè)隱含的value參數(shù)中給出。
與讀取和寫(xiě)入域的方法一樣,屬性也可以用同樣的語(yǔ)法讀取和寫(xiě)入。例如,下面的代碼實(shí)例化了一個(gè)PropertyDemo類(lèi),然后寫(xiě)入、讀取它的Prop屬性。
PropertyDemo pd = new PropertyDemo();
pd.Prop = "123"; // set
string s = pd.Prop; // get
3.4、以引用方式傳遞簡(jiǎn)單數(shù)據(jù)類(lèi)型的參數(shù)
在Java中,當(dāng)你把一個(gè)簡(jiǎn)單數(shù)據(jù)類(lèi)型的值作為參數(shù)傳遞給方法時(shí),參數(shù)總是以值的方式傳遞――即,系統(tǒng)將為被調(diào)用的方法創(chuàng)建一個(gè)參數(shù)值的副本。在C#中,你可以用引用的方式傳遞一個(gè)簡(jiǎn)單數(shù)據(jù)類(lèi)型的值。此時(shí),被調(diào)用的方法將直接使用傳遞給它的那個(gè)值――也就是說(shuō),如果在被調(diào)用方法內(nèi)部修改了參數(shù)的值,則原來(lái)的變量值也隨之改變。
在C#中以引用方式傳遞值時(shí),我們使用ref關(guān)鍵詞。例如,如果編譯并運(yùn)行下面的代碼,你將在控制臺(tái)上看到輸出結(jié)果16。注意i值被傳遞給ProcessNumber之后是如何被改變的。
using System;
public class PassByReference {
public static void Main(String[] args) {
int i = 8;
ProcessNumber(ref i);
Console.WriteLine(i);
}
public static void ProcessNumber(ref int j) {
j = 16;
}
}
C#中還有一個(gè)允許以引用方式傳遞參數(shù)的關(guān)鍵詞out,它與ref相似。但是,使用out時(shí),作為參數(shù)傳遞的變量在傳遞之前不必具有已知的值。在上例中,如果整數(shù)i在傳遞給ProcessNumber方法之前沒(méi)有初始化,則代碼將出錯(cuò)。如果用out來(lái)取代ref,你就可以傳遞一個(gè)未經(jīng)初始化的值,如下面這個(gè)修改后的例子所示。
using System;
public class PassByReference {
public static void Main(String[] args) {
int i;
ProcessNumber(out i);
Console.WriteLine(i);
}
public static void ProcessNumber(out int j) {
j = 16;
}
}
經(jīng)過(guò)修改之后,雖然i值在傳遞給ProcessNumber方法之前沒(méi)有初始化,但PassByReference類(lèi)能夠順利通過(guò)編譯。
3.5、C#保留了指針
對(duì)于那些覺(jué)得自己能夠恰到好處地運(yùn)用指針并樂(lè)意手工進(jìn)行內(nèi)存管理的開(kāi)發(fā)者來(lái)說(shuō),在C#中,他們?nèi)耘f可以用既不安全也不容易使用的“古老的”指針來(lái)提高程序的性能。C#提供了支持“不安全”(unsafe)代碼的能力,這種代碼能夠直接操作指針,能夠“固定”對(duì)象以便臨時(shí)地阻止垃圾收集器移動(dòng)對(duì)象。無(wú)論從開(kāi)發(fā)者還是用戶(hù)的眼光來(lái)看,這種對(duì)“不安全”代碼的支持其實(shí)是一種安全功能?!安话踩钡拇a必須用unsafe關(guān)鍵詞顯式地標(biāo)明,因此開(kāi)發(fā)者不可能在無(wú)意之中使用“不安全”的代碼。同時(shí),C#編譯器又和執(zhí)行引擎協(xié)作,保證了“不安全”的代碼不能偽裝成為安全代碼。
using System;
class UsePointer {
unsafe static void PointerDemo(byte[] arr) {
.
.
}
}
C#中的unsafe代碼適合在下列情形下使用:當(dāng)速度極端重要時(shí),或者當(dāng)對(duì)象需要與現(xiàn)有的軟件(比如COM對(duì)象或者DLL形式的C代碼)交互時(shí)。
3.6、代理
代理(delegate)可以看作C++或者其他語(yǔ)言中的函數(shù)指針。然而,與函數(shù)指針不同的是,C#中的代理是面向?qū)ο蟮?、?lèi)型安全的、可靠的。而且,函數(shù)指針只能用來(lái)引用靜態(tài)函數(shù),但代理既能夠引用靜態(tài)方法,也能夠引用實(shí)例方法。代理用來(lái)封裝可調(diào)用方法。你可以在類(lèi)里面編寫(xiě)方法并在該方法上創(chuàng)建代理,此后這個(gè)代理就可以被傳遞到第二個(gè)方法。這樣,第二個(gè)方法就可以調(diào)用第一個(gè)方法。
代理是從公共基類(lèi)System.Delegate派生的引用類(lèi)型。定義和使用代理包括三個(gè)步驟:聲明,創(chuàng)建實(shí)例,調(diào)用。代理用delegate聲明語(yǔ)法聲明。例如,一個(gè)不需要參數(shù)且沒(méi)有返回值的代理可以用如下代碼聲明:
delegate void TheDelegate();
創(chuàng)建代理實(shí)例的語(yǔ)法是:使用new關(guān)鍵詞,并引用一個(gè)實(shí)例或類(lèi)方法,該方法必須符合代理指定的特征。一旦創(chuàng)建了代理的實(shí)例,我們就可以用調(diào)用方法的語(yǔ)法調(diào)用它。
3.7、包裝和解除包裝
在面向?qū)ο蟮木幊陶Z(yǔ)言中,我們通常使用的是對(duì)象。但為了提高速度,C#也提供了簡(jiǎn)單數(shù)據(jù)類(lèi)型。因此,C#程序既包含一大堆的對(duì)象,又有大量的值。在這種環(huán)境下,讓這兩者協(xié)同工作始終是一個(gè)不可回避的問(wèn)題,你必須要有一種讓引用和值進(jìn)行通信的方法。
在C#以及.NET運(yùn)行時(shí)環(huán)境中,這個(gè)“通信”問(wèn)題通過(guò)包裝(Boxing)和解除包裝(Unboxing)解決。包裝是一種讓值類(lèi)型看起來(lái)象引用類(lèi)型的處理過(guò)程。當(dāng)一個(gè)值類(lèi)型(簡(jiǎn)單數(shù)據(jù)類(lèi)型)被用于一個(gè)要求或者可以使用對(duì)象的場(chǎng)合時(shí),包裝操作自動(dòng)進(jìn)行。包裝一個(gè)value-type值的步驟包括:分配一個(gè)對(duì)象實(shí)例,然后把value-type值復(fù)制到對(duì)象實(shí)例。
解除包裝所執(zhí)行的動(dòng)作與包裝相反,它把一個(gè)引用類(lèi)型轉(zhuǎn)換成值類(lèi)型。解除包裝操作的步驟包括:首先檢查并確認(rèn)對(duì)象實(shí)例確實(shí)是給定value-type的一個(gè)經(jīng)過(guò)包裝的值,然后從對(duì)象實(shí)例復(fù)制出值。
Java對(duì)該問(wèn)題的處理方式略有不同。Java為每一種簡(jiǎn)單數(shù)據(jù)類(lèi)型提供了一個(gè)對(duì)應(yīng)的類(lèi)封裝器。例如,用Integer類(lèi)封裝int類(lèi)型,用Byte類(lèi)封裝byte類(lèi)型。
【結(jié)束語(yǔ)】本文為你比較了C#和Java。這兩種語(yǔ)言很相似,然而,說(shuō)C#是Java的克隆或許已經(jīng)大大地言過(guò)其實(shí)。面向?qū)ο蟆⒅虚g語(yǔ)言這類(lèi)概念并不是什么新東西。如果你準(zhǔn)備設(shè)計(jì)一種面向?qū)ο蟮男抡Z(yǔ)言,而且它必須在一個(gè)受管理的安全環(huán)境內(nèi)運(yùn)行,你難道不會(huì)搞出與