引言:每隔10年左右,編程人員就需要花費大量的時間和精力去學習新的編程技術。在80年代是Unix和C,90年代是Windows和C++,現在又輪到了微軟的.NETFramework和C#。盡管需要學習新的技術,但由此帶來的好處卻遠高於付出的勞動。幸運的是,使用C#和.NET進行的大多數工程的分析和設計與在C++和Windows中沒有本質的變化。在本篇文章中,我將介紹如何實現由C++到C#的飛躍。
系列文章:[由C++轉向C#需要注意的變化(一)(二)(三)(四)]
IEnumerable界面
再回到上面的例子中。象在普通的數組中那樣,使用foreach-loop循環結構就能夠很好地打印ListBoxTest類中的字符串,通過在類中實現IEnumerable界面就能實現,這是由foreach-loop循環結構隱性地完成的。在任何支持枚舉和foreach-loop循環的類中都可以實現IEnumerable界面。
IEnumerable界面只有一個方法GetEnumerator,其任務是返回一個特別的IEnumerator的實現。從語法的角度來看,Enumerable類能夠提供一個IEnumerator。
Figure5ListBoxClass
usingSystem;
//簡化的ListBox控制
publicclassListBoxTest
{
//用字符串初始化該ListBox
publicListBoxTest(paramsstring[]initialStrings)
{
//為字符串分配空間
myStrings=newString[256];
//把字符串拷貝到構造器中
foreach(stringsininitialStrings)
{
myStrings[myCtr++]=s;
}
}
//在ListBox的末尾添加一個字符串
publicvoidAdd(stringtheString)
{
myStrings[myCtr++]=theString;
}
publicstringthis[intindex]
{
get
{
if(index<0||index>=myStrings.Length)
{
//處理有問題的索引
}
returnmyStrings[index];
}
set
{
myStrings[index]=value;
}
}
//返回有多少個字符串
publicintGetNumEntries()
{
returnmyCtr;
}
privatestring[]myStrings;
privateintmyCtr=0;
}
publicclassTester
{
staticvoidMain()
{
//創建一個新的列表並初始化
ListBoxTestlbt=newListBoxTest("Hello","World");
//添加一些新字符串
lbt.Add("Who");
lbt.Add("Is");
lbt.Add("John");
lbt.Add("Galt");
stringsubst="Universe";
lbt[1]=subst;
//訪問所有的字符串
for(inti=0;i
Console.WriteLine("lbt[{0}]:{1}",i,lbt[i]);
}
}
}
Enumerator必須實現IEnumerator方法,這可以直接通過一個容器類或一個獨立的類實現,後一種方法經常被選用,因為它可以將這一任務封裝在Enumerator類中,而不會使容器類顯得很混亂。我們將在上面代碼中的ListBoxTest中添加Enumerator類,由於Enumerator類是針對我們的容器類的(因為ListBoxEnumerator必須清楚ListBoxTest的許多情況),我們將使它在ListBoxTest中成為不公開的。在本例中,ListBoxTest被定義來完成IEnumerable界面,IEnumerable界面必須返回一個Enumerator。
publicIEnumeratorGetEnumerator()
{
return(IEnumerator)newListBoxEnumerator(this);
}
注意,方法將當前的ListBoxTest對象(this)傳遞給Enumerator,這將使Enumerator枚舉這一指定的ListBoxTest對象中的元素。
實現這一類的Enumerator在這裡被實現為ListBoxEnumerator,它在ListBoxTest中被定義成一個私有類,這一工作是相當簡單的。
被枚舉的ListBoxTest作為一個參數被傳遞給constructor,ListBoxTest被賦給變量myLBT,構造器還會將成員變量index設置為-1,表明對象的枚舉還沒有開始。
publicListBoxEnumerator(ListBoxTesttheLB)
{
myLBT=theLB;
index=-1;
}
MoveNext方法對index進行加1的操作,然後確保沒有超過枚舉的對象的邊界。如果超過邊界了,就會返回false值,否則返回true值。
publicboolMoveNext()
{
index++;
if(index>=myLBT.myStrings.Length)
returnfalse;
else
returntrue;
}
Reset的作用僅僅是將index的值設置為-1。
Current返回最近添加的字符串,這是一個任意的設定,在其他類中,Current可以有設計人員確定的意義。無論是如何設計的,每個進行枚舉的方法必須能夠返回當前的成員。
publicobjectCurrent
{
get
{
return(myLBT[index]);
}
}
對foreach循環結構的調用能夠獲取枚舉的方法,並用它處理數組中的每個成員。由於foreach循環結構將顯示每一個字符串,而無論我們是否添加了一個有意義的值,我們將myStrings的初始化改為8個條目,以保證顯示的易於處理。
myStrings=newString[8];
使用基本類庫
為了更好地理解C#與C++的區別和解決問題方式的變化,我們先來看一個比較簡單的例子。我們將創建一個讀取文本文件的類,並在屏幕上顯示其內容。我將把它做成多線程程序,以便在從磁盤上讀取數據時還可以做其他的工作。
在C++中,我們可能會創建一個讀文件的線程和另一個做其他工作的線程,這二個線程將各自獨立地運行,但可能會需要對它們進行同步。在C#中,我們也可以完成同樣的工作,由於.NET框架提供了功能強大的異步I/O機制,在編寫線程時,我們會節省不少的時間。
異步I/O支持是內置在CLR中的,而且幾乎與使用正常的I/O流類一樣簡單。在程序的開始,我們首先通知編譯器,我們將在程序中使用許多名字空間中的對象:
usingSystem;
usingSystem.IO;
usingSystem.Text;
在程序中包含System,並不會自動地包含其所有的子名字空間,必須使用using關健字明確地包含每個子名字空間。我們在例子中會用到I/O流類,因此需要包含System.IO名字空間,我們還需要System.Text名字空間支持字節流的ASCII編碼。
由於.NET架構為完成了大部分的工作,編寫這一程序所需的步驟相當簡單。我們將用到Stream類的BeginRead方法,它提供異步I/O功能,將數據讀入到一個緩沖區中,當緩沖區可以處理時調用相應的處理程序。
我們需要使用一個字節數組作為緩沖區和回叫方法的代理,並將這二者定義為驅動程序類的private成員變量。
publicclassAsynchIOTester
{
privateStreaminputStream;
privatebyte[]buffer;
privateAsyncCallbackmyCallBack;
inputStream是一個Stream類型的變量,我們將對它調用BeginRead方法。代理與成員函數的指針非常相似。代理是C#的第一類元素。
當緩沖區被磁盤上的文件填滿時,.NET將調用被代理的方法對數據進行處理。在等待讀取數據期間,我們可以讓計算機完成其他的工作。(在本例中是將1個整型變量由1增加到50000,但在實際的應用程序中,我們可以讓計算機與用戶進行交互或作其他有意義的工作。)
本例中的代理被定義為AsyncCallback類型的過程,這是Stream的BeginRead方法所需要的。System空間中AsyncCallback類型代理的定義如下所示:
publicdelegatevoidAsyncCallback(IAsyncResultar);
這一代理可以是與任何返回void類型值、將IAsyncResult界面作為參數的方法相關聯的。在該方法被調用時,CLR可以在運行時傳遞IAsyncResult界面對象作為參數。我們需要如下所示的形式定義該方法:
voidOnCompletedRead(IAsyncResultasyncResult)
然後在構造器中與代理連接起來:
AsynchIOTester()
{
???
myCallBack=newAsyncCallback(this.OnCompletedRead);
}
上面的代碼將代理的實例賦給成員變量myCallback。下面是全部程序的詳細工作原理。在Main函數中,創建了一個類的實例,並讓它開始運行:
publicstaticvoidMain()
{
AsynchIOTestertheApp=newAsynchIOTester();
theApp.Run();
}
new關健字能夠啟動構造器。在構造器中我們打開一個文件,並得到一個Stream對象。然後在緩沖中分配空間並與回調機制聯結起來。
AsynchIOTester()
{
inputStream=File.OpenRead(@"C:MSDNfromCppToCS.txt");
buffer=newbyte[BUFFER_SIZE];
myCallBack=newAsyncCallback(this.OnCompletedRead);
}
在Run方法中,我們調用了BeginRead,它將以異步的方式讀取文件。
inputStream.BeginRead(
buffer,//存放結果
0,//偏移量
buffer.Length,//緩沖區中有多少字節
myCallBack,//回調代理
null);//本地對象
這時,我們可以完成其他的工作。
for(longi=0;i<50000;