引言:每隔10年左右,編程人員就需要花費大量的時間和精力去學習新的編程技術。在80年代是Unix和C,90年代是Windows和C++,現在又輪到了微軟的.NETFramework和C#。盡管需要學習新的技術,但由此帶來的好處卻遠高於付出的勞動。幸運的是,使用C#和.NET進行的大多數工程的分析和設計與在C++和Windows中沒有本質的變化。在本篇文章中,我將介紹如何實現由C++到C#的飛躍。
已經有許多文章介紹過C#對C++的改進,在這裡我就不再重復這些問題了。在這裡,我將重點討論由C++轉向C#時最大的變化:由不可管理的環境向可管理的環境的變化。此外,我還會提出一些C#編程人員容易犯的錯誤供大家參考,此外,還將說明一些C#語言的能夠影響編程的新功能。
系列文章:[由C++轉向C#需要注意的變化(一)(二)(三)(四)]
在網絡上讀取文件
在C++中,在網絡上讀取文件需要有相當的編程技巧,.NET對此提供了廣泛的支持。事實上,在網絡上讀取文件僅僅是基礎類庫中Stream類的另一種應用。
首先,為了對TCP/IP端口(在本例中是65000)進行監聽,我們需要創建一個TCPListener類的實例。
TCPListenertcpListener=newTCPListener(65000);
一旦創建後,就讓它開始進行監聽。
tcpListener.Start();
現在就要等待客戶連接的要求了。
SocketsocketForClient=tcpListener.Accept();
TCPListener對象的Accept方法返回一個Socket對象,Accept是一個同步的方法,除非接收到一個連接請求它才會返回。如果連接成功,就可以開始向客戶發送文件了。
if(socketForClient.Connected)
{
???
接下來,我們需要創建一個NetworkStream類,將報路傳遞給constructor:
NetworkStreamnetworkStream=newNetworkStream(socketForClient);
然後創建一個StreamWriter對象,只是這次不是在文件上而是在剛才創建的NetworkStream類上創建該對象:
System.IO.StreamWriterstreamWriter=
newSystem.IO.StreamWriter(networkStream);
當向該流寫內容時,流就通過網絡被傳輸給客戶端。
客戶端的創建
客戶端軟件就是一個TCPClient類的具體例子,TCPClient類代表連向主機的一個TCP/IP連接。
TCPClientsocketForServer;
socketForServer=newTCPClient("localHost",65000);
有了TCPClient對象後,我們就可以創建NetworkStream對象了,然後在其上創建StreamReader類:
NetworkStreamnetworkStream=socketForServer.GetStream();
System.IO.StreamReaderstreamReader=
newSystem.IO.StreamReader(networkStream);
現在,只要其中有數據就讀取該流,並將結果輸出到控制台上。
do
{
outputString=streamReader.ReadLine();
if(outputString!=null)
{
Console.WriteLine(outputString);
}
}
while(outputString!=null);
為了對這一段代碼進行測試,可以創建如下一個測試用的文件:
Thisislineone
Thisislinetwo
Thisislinethree
Thisislinefour
這是來自服務器的輸出:
Output(Server)
Clientconnected
SendingThisislineone
SendingThisislinetwo
SendingThisislinethree
SendingThisislinefour
Disconnectingfromclient...
Exiting...
下面是來自客戶端的輸出:
Thisislineone
Thisislinetwo
Thisislinethree
Thisislinefour
屬性和元數據
C#和C++之間一個顯著的區別是它提供了對元數據的支持:有關類、對象、方法等其他實體的數據。屬性可以分為二類:一類以CLR的一部分的形式出現,另一種是我們自己創建的屬性,CLR屬性用來支持串行化、排列和COM協同性等。一些屬性是針對一個組合體的,有些屬性則是針對類或界面,它們也被稱作是屬性目標。
將屬性放在屬性目標前的方括號內,屬性就可以作用於它們的屬性目標。
[assembly:AssemblyDelaySign(false)]
[assembly:AssemblyKeyFile(".\keyFile.snk")]
或用逗號將各個屬性分開:
[assembly:AssemblyDelaySign(false),
assembly:AssemblyKeyFile(".\keyFile.snk")]
自定義的屬性
我們可以任意創建自定義屬性,並在認為合適的時候使用它們。假設我們需要跟蹤bug的修復情況,就需要建立一個包含bug的數據庫,但需要將bug報告與專門的修正情況綁定在一塊兒,則可能在代碼中添加如下所示的注釋:
//Bug323fixedbyJesseLiberty1/1/2005.
這樣,在源代碼中就可以一目了然地了解bug的修正情況,但如果如果把相關的資料保存在數據庫中可能會更好,這樣就更方便我們的查詢工作了。如果所有的bug報告都使用相同的語法那就更好了,但這時我們就需要一個定制的屬性了。我們可能使用下面的內容代替代碼中的注釋:
[BugFix(323,"JesseLiberty","1/1/2005")Comment="Offbyoneerror"]
與C#中的其他元素一樣,屬性也是類。定制化的屬性類需要繼承System.Attribute:
publicclassBugFixAttribute:System.Attribute
我們需要讓編譯器知道這個屬性可以跟什麼類型的元素,我們可以通過如下的方式來指定該類型的元素:
[AttributeUsage(AttributeTargets.ClassMembers,AllowMultiple=true)]
AttributeUsage是一個作用於屬性的屬性━━元屬性,它提供的是元數據的元數據,也即有關元數據的數據。在這種情況下,我們需要傳遞二個參數,第一個是目標(在本例中是類成員。),第二個是表示一個給定的元素是否可以接受多於一個屬性的標記。AllowMultiple的值被設置為true,意味著類成員可以有多於一個BugFixAttribute屬性。如果要聯合二個屬性目標,可以使用OR操作符連接它們。
[AttributeUsage(AttributeTargets.Class|AttributeTargets.Interface,AllowMultiple=true)]
上面的代碼將使一個屬性隸屬於一個類或一個界面。
新的自定義屬性被命名為BugFixAttribute。命名的規則是在屬性名之後添加Attribute。在將屬性指派給一個元素後,編譯器允許我們使用精簡的屬性名調用這一屬性。因此,下面的代碼是合法的:
[BugFix(123,"JesseLiberty","01/01/05",Comment="Offbyone")]
編譯器將首先查找名字為BugFix的屬性,如果沒有發現,則查找BugFixAttribute。
每個屬性必須至少有一個構造器。屬性可以接受二種類型的參數:環境參數和命名參數。在前面的例子中,bugID、編程人員的名字和日期是環境參數,注釋是命名參數。環境參數被傳遞到構造器中的,而且必須按在構造器中定義的順序傳遞。
publicBugFixAttribute(intbugID,stringprogrammer,stringdate)
{
this.bugID=bugID;
this.programmer=programmer;
this.date=date;
}
Namedparametersareimplementedasproperties.