C# 程式設計語言的未來功能 2

來源:互聯網
上載者:User
編程 聲明約束
在 C# 中,程式可以為泛型類中聲明的每個型別參數提供可選約束列表。約束表示要將一個類型構造成泛型所必須滿足的要求。可以使用 where 關鍵字聲明約束,該關鍵字後跟“參數-要求”對,其中“參數”必須是泛型中定義的某個參數,“要求”必須是類或介面。

為了滿足在 Dictionary 類中使用 CompareTo 方法的需要,程式可以對 KeyType 型別參數添加約束,要求傳遞給 Dictionary 類作為第一個參數的任何類型都必須實現 IComparable 介面,例如:

public class Dictionary<KeyType, ValType> where KeyType : IComparable
{
   public void Add(KeyType key, ValType val)
   {
      ...
      switch(key.CompareTo(x))
      {
      }
      ...
   }
}
這樣,編譯代碼時就會檢查代碼,以確保程式每次使用 Dictionary 類時,作為第一個參數傳遞的類型都實現了 IComparable 介面。此外,程式在調用 CompareTo 方法之前,再也無需將變數顯式轉換為 IComparable 介面了。

多重約束

對於任何給定的型別參數,程式可以為其指定任意多個介面約束,但最多隻能指定一個類約束。每個新約束都以另一個“參數-要求”對的形式進行聲明,並且給定的泛型的每個約束都用逗號分隔。以下樣本中的 Dictionary 類包含兩種參數,KeyType 和 ValType。KeyType 型別參數有兩個介面約束,而 ValType 型別參數有一個類約束:

public class Dictionary<KeyType, ValType> where
KeyType : IComparable,
KeyType : IEnumerable,
ValType : Customer
{
   public void Add(KeyType key, ValType val)
   {
      ...
      switch(key.CompareTo(x))
      {
      }
      ...
   }
}
運行時的泛型
泛型類的編譯方法與常規類的編譯方法幾乎沒有差別。事實上,編譯結果只不過是中繼資料和中繼語言 (IL)。當然,為了接受代碼中使用者提供的類型,應對 IL 進行參數化。根據提供的型別參數是實值型別還是參考型別,泛型的 IL 的用法會有所不同。

當將實值型別作為參數首次構造泛型時,運行時將使用提供的參數替換 IL 中的相應位置來建立一個專用的泛型。針對每個用作參數的唯一實值型別,將一次性建立專用的泛型。

例如,假設程式碼聲明了一個由整數構造的 Stack:

Stack<int> stack;
此時,運行時將產生一個專用的 Stack 類,並用整數替換此類的相應參數。現在,無論程式碼何時使用整數 Stack,運行時都將重複使用產生的專用 Stack 類。以下樣本將建立整數 Stack 的兩個執行個體,每個執行個體均使用由此整數 Stack 的運行時所產生的程式碼來建立:

Stack<int> stackOne = new Stack<int>();
Stack<int> stackTwo = new Stack<int>();
但是,如果在程式碼中的其他位置又建立了一個 Stack 類,並使用不同的實值型別(例如長整型或使用者定義的結構)作為其參數,則運行時將產生其他形式的泛型,而這時會替換 IL 相應位置中的長整型參數。為使用實值型別構造的泛型建立專用類的優點是可以獲得更好的效能。畢竟每個專用的泛型類都是在“本地”包含實值型別,因此不必再進行轉換。

泛型與參考型別的工作方式稍有不同。首次使用任何參考型別構造泛型時,運行時用對象引用替換 IL 中的參數來建立專用的泛型。之後,每當使用參考型別作為參數執行個體化構造的類型時,無論構造的是何種類型,運行時都會重複使用先前建立的專用泛型。

例如,假設有兩個參考型別,Customer 類和 Order 類,並進一步假設您建立了 Customer 類型的 Stack:

Stack<Customer> customers;
此時,運行時將產生專用 Stack 類,該類並不儲存資料,而是儲存隨後填充的對象引用。假設下一行代碼建立了一個其他參考型別的 Stack,稱為 Order:

Stack<Order> orders = new Stack<Order>();
與實值型別不同,沒有為 Order 類型建立另一個專用的 Stack 類,而是建立了專用 Stack 類的執行個體並設定 orders 變數來引用它。對於替換型別參數的每個對象引用,按照 Order 類型的大小分配記憶體空間,並將指標設定為引用該記憶體位置。假設您隨後遇到了一行用於建立 Customer 類型的 Stack 的代碼:

customers = new Stack<Customer>();
同上一個使用 Order 類型建立的 Stack 類一樣,建立了專用 Stack 類的另一個執行個體,並將其中包含的指標設定為引用 Customer 類型大小的記憶體地區。由於不同的程式在參考型別的數量上存在著很大差異,因此泛型的 C# 實現通過將參考型別的數量減少到編譯器為參考型別的泛型類建立的專用類數量,大大降低了代碼的膨脹速度。

此外,當使用型別參數(無論是實值型別還是參考型別)執行個體化泛型 C# 類時,可以在運行時使用反射和實際類型進行查詢,並且可以確定其型別參數。

C# 泛型與其他實現之間的差異

C++ 範本與 C# 泛型存在著顯著的差別。C# 泛型被編譯成 IL,這使得在運行時會智能地為每個實值型別建立相應的專用類型,而為參考型別只會建立一次專用類型;C++ 範本實際上是代碼擴充宏,它為提供給模板的每個型別參數產生一個專用類型。因此,當 C++ 編譯器遇到模板(例如整數 Stack)時,它會將模板代碼擴充為 Stack 類並將整數作為該類本身的類型包含在其中。無論型別參數是實值型別還是參考型別,如果不專門設計連結器來降低代碼膨脹速度,C++ 編譯器每次都會建立一個專用類,從而導致比使用 C# 泛型更顯著的代碼膨脹速度。

而且,C++ 範本不能定義約束。C++ 範本只能通過使用一個成員(可能屬於也可能不屬於型別參數),隱式定義約束。如果最終傳遞給泛型類的型別參數中存在該成員,程式將正常運行。否則,程式將失敗,並可能返回隱藏的錯誤資訊。由於 C# 泛型可以聲明約束,並且具有嚴格的類型,因此不存在這些潛在的錯誤。

現在,Sun Microsystems® 已經在新版本的 Java 語言(代碼名稱為“Tiger”)中添加了其他的泛型。Sun 選擇的實現不需要修改 JAVA 虛擬機器。因此,Sun 面臨著如何在未修改的虛擬機器上實現泛型的問題。

提出的 Java 實現使用與 C++ 中的模板和 C# 中的泛型類似的文法,包括型別參數和約束。然而,由於它處理實值型別與處理參考型別的方式不一樣,因此未修改的 JAVA 虛擬機器不支援實值型別的泛型。因此,Java 中的泛型無法得到有效執行。事實上,Java 編譯器會在需要返回資料時,從指定的約束(如果聲明了)或基本物件類型(如果未聲明約束)插入自動向下的類型轉換。此外,Java 編譯器將在運行時產生一個專用類型,然後使用它執行個體化任何構造類型。最後,由於 JAVA 虛擬機器本身不支援泛型,因此無法在運行時確定泛型執行個體的型別參數,而且反射的其他用途也會受到嚴重限制。

其他語言中的泛型支援
Microsoft 的目標是在 Visual J#(TM)、Visual C++ 和 Visual Basic 中支援使用和建立泛型。儘管不同語言實現此功能的時間有早有晚,但 Microsoft 的所有其他三種語言都將包含對泛型的支援。同時,C# 小組正努力在泛型的基礎運行時中加入相應的功能,為實現多語言支援奠定基礎。Microsoft 與第三方語言夥伴緊密協作,以確保在基於 .NET 的語言中建立和使用泛型。

迭代程式
迭代程式是基於研究語言中的類似功能(例如 CLU、Sather 和 Icon)而構造的語言。簡單說來,通過迭代程式,類型可輕鬆地聲明 foreach 語句對其元素進行迭代的方式。

為什麼需要迭代程式
現在,如果類需要使用 foreach 迴圈結構支援迭代操作,則它們必須實現“列舉程式模式”。例如,編譯器將左側的 foreach 迴圈結構擴充為右側的 while 迴圈結構:

List list = ...;
foreach(object obj in list)
{
DoSomething(obj);
}


Enumerator e = list.GetEnumerator();
while(e.MoveNext())
{
   object obj = e.Current;
   DoSomething(obj);




值得注意的是,為了使 foreach 迴圈能夠正常運行,List 資料結構(所迭代的執行個體)必須支援 GetEnumerator 函數。建立 List 資料結構後,必須實現 GetEnumerator 函數,以返回 ListEnumerator 對象:

public class List
{
   internal object[] elements;
   internal int count;

   public ListEnumerator GetEnumerator()
   {
      return new ListEnumerator(this);
   }
}
所建立的 ListEnumerator 對象不僅必須實現 Current 屬性和 MoveNext 方法,而且還必須維護其內部狀態,以便程式在每次執行該迴圈時都可以移到下一項。此內部狀態機器對於 List 資料結構而言比較簡單,但對於需要遞迴迴圈的資料結構(例如二叉樹)來說,該狀態機器將相當複雜。

由於實現此列舉程式模式需要開發人員投入大量的精力並編寫大量代碼,因此 C# 包含一種新的結構,使得類可以輕鬆地指示 foreach 迴圈對其內容進行迭代的方式。

定義迭代程式
由於迭代程式是 foreach 迴圈結構的邏輯對應物,因此其定義方式類似於函數:使用 foreach 關鍵字並在後面帶有一對圓括弧。在以下樣本中,程式將為 List 型別宣告一個迭代程式。迭代程式的傳回型別由使用者決定,但是由於 List 類內部儲存的是物件類型,因此以下迭代程式樣本的傳回型別為對象:

public class List
{
   internal object[] elements;
   internal int count;

   public object foreach()
   {
   }
}
值得注意的是,實現列舉程式模式後,程式需要維護內部狀態機器以便跟蹤程式在資料結構中的位置。迭代程式具有內建狀態機器。使用新的 yield 關鍵字,程式可以將值返回到調用該迭代程式的 foreach 語句。當 foreach 語句下次迴圈並再次調用迭代程式時,此迭代程式將在上一個 yield 語句停止的位置開始執行。在以下樣本中,程式將產生三個字串類型:

public class List
{
   internal object[] elements;
   internal int count;

   public string foreach()
   {
      yield "microsoft";
      yield "corporation";
      yield "developer division";
   }
}
在以下樣本中,調用此迭代程式的 foreach 迴圈將執行三次,每次都會按照前三個 yield 語句指定的順序接收字串:

List list = new List();
foreach(string s in list)
{
Console.WriteLine(s);
}
如果要讓程式實現迭代程式以遍曆列表中的元素,則需要使用 foreach 迴圈修改此迭代程式使其遍曆元素數組,並在每次迭代中產生數組中的每個項目:

public class List
{
   internal object[] elements;
   internal int count;

   public object foreach()
   {
      foreach(object o in elements)
      {
         yield o;
      }
   }
}
迭代程式的工作原理
迭代程式代表所在的程式處理實現列舉程式模式的日常操作。C# 編譯器將您在迭代程式中編寫的代碼轉換成使用列舉程式模式的相應類和代碼,而無需建立類和建立狀態機器。通過這種方式,迭代程式顯著提高了開發人員的工作效率。

匿名方法
匿名方法是另一種實用的語言結構,它使程式員能夠建立可裝箱在委託中、並且可在以後執行的代碼塊。它們基於稱作 λ 函數的語言概念,並且類似於 Lisp 和 Python 中的對應語言概念。

建立委託代碼
委託是引用方法的對象。調用委託時,將調用它所引用的方法。以下樣本舉例說明了一個簡單的表單,其中包含列表框、文字框和按鈕三個控制項。初始化按鈕時,程式將指示其 Click 委託引用該對象中其他位置儲存的 AddClick 方法。在 AddClick 方法中,文字框的值儲存在列表框中。由於 AddClick 方法被添加到按鈕執行個體的 Click 委託中,因此每次單擊該按鈕時都將調用此方法。

public class MyForm
{
   ListBox listBox;
   TextBox textBox;
   Button button;

   public MyForm()
   {
listBox = new ListBox(...);
textBox = new TextBox(...);
button = new Button(...);
button.Click += new EventHandler(AddClick);
}

   void AddClick(object sender, EventArgs e)
   {
      listBox.Items.Add(textBox.Text);
   }
}
使用匿名方法
上一個樣本非常直觀。其中建立了一個單獨的函數,並對其進行了委託引用,每當調用此委託時,程式都會調用該函數。在該函數中,執行了一系列的可執行步驟。使用匿名方法,程式無需為該類建立整個新方法,而可以直接引用委託中包含的可執行步驟。匿名方法的聲明方法是先執行個體化一個委託,然後在執行個體化語句之後加上一對錶示執行範圍的花括弧,最後加上一個用於終止語句的分號。

在以下樣本中,程式修改委託建立語句以直接修改列表框,而不是引用代表程式來修改該列表框的函數。儲存代碼的目的是為了修改委託建立語句之後的執行範圍中的列表框。

public class MyForm
{
   ListBox listBox;
   TextBox textBox;
   Button button;

   public MyForm()
   {
listBox = new ListBox(...);
textBox = new TextBox(...);
button = new Button(...);
button.Click += new EventHandler(sender, e)
{
         listBox.Items.Add(textBox.Text);
};
}
}
請注意,“匿名”方法中的代碼是如何訪問和處理其執行範圍以外聲明的變數的。實際上,匿名方法可以引用由類和參數聲明的變數,也可以引用所在方法聲明的局部變數。

向匿名方法傳遞參數
有趣的是,“匿名”方法語句包含兩個參數,即 sender 和 e。查看 Button 類的 Click 委託的定義,您會發現委託引用的任何函數都必須包含兩個參數,第一個參數為物件類型,第二個參數為 EventArgs 類型。在第一個樣本中,程式未使用“匿名”方法,而是向 AddClick 方法傳遞了兩個參數,類型分別為對象和 EventArgs。

即使以內聯方式編寫此代碼,委託仍必須接收兩個參數。在“匿名”方法中,必須聲明兩個參數的名稱,這樣關聯的代碼塊才能使用它們。當觸發按鈕上的 Click 事件時,將調用“匿名”方法並將相應的參數傳遞給該方法。

匿名方法的工作原理
遇到“匿名”委託時,C# 編譯器會自動將其執行範圍內的代碼轉換為唯一命名類中的唯一命名函數。然後將設定儲存代碼塊的委託,以引用編譯器產生的對象和方法。調用委託時,將通過編譯器產生的方法執行“匿名”方法塊。

局部類型
儘管在單個檔案中維護類型的所有原始碼是物件導向編程的好方法,但有時效能約束會使得類型變大。此外,在某些情況下將類型分割成子類型所耗費的開銷是無法讓人接受的。而且,程式員經常會建立或使用應用程式來發布原始碼和修改結果代碼。遺憾的是,當再次發布原始碼時,所有現有的原始碼修改將被覆蓋。

局部類型允許您將包含大量原始碼的類型分割成多個不同的源檔案,以便於開發和維護。此外,局部類型可用於將電腦產生的類型部分與使用者編寫的類型部分分隔開,從而更易於補充或修改工具產生的程式碼。

在以下樣本中,兩個 C# 代碼檔案 File1.cs 和 File2.cs 中都定義了名為 Foo 的類。如果不使用局部類型,將會出現編譯錯誤,因為這兩個類存在於同一個命名空間中。使用 partial 關鍵字,可以指示編譯器:別處可能包含此類的其他定義。

File1.cs File2.cs
public partial class Foo
{
public void MyFunction()
{
// 在此處執行操作
}
}


public partial class Foo
{
   public void MyOtherFunction()
   {
      // 在此處執行操作
   }
}




編譯時間,C# 編譯器將收集局部類型的所有定義並將它們組合在一起。編譯器產生的結果 IL 顯示了組合而成的單個類,而不是將多個類分別作為單獨的類進行連續顯示。

符合標準
2001 年 12 月,歐洲電腦製造商協會 (ECMA) 將 C# 程式設計語言批准為一項標準 (ECMA 334)。此後不久,C# 標準便得到國際標準組織 (ISO) 的快速跟蹤處理,預計很快就會得到批准。C# 標準的建立是新程式設計語言發展史中的重要裡程碑,它預示著未來有望在各種作業系統平台上編寫多種實現。實際上,我們從其簡短的曆史中可以看到,許多第三方編譯器供應商和研究人員已經將它當作標準來實現並建立了自己的 C# 編譯器版本。

Microsoft 歡迎客戶對在 C# 語言中添加上面提到的功能提供反饋意見,並打算將這些功能提交給進行中的語言標準化進程。

可用性
下面介紹的功能將在 C# 編譯器的未來版本中實現。2003 年年初,Visual Studio .NET 的“Everett”版本將包含為完全符合 ECMA 標準而略作修改的 C# 版本。此版本不包含本文介紹的功能。Microsoft 打算在 Visual Studio 的“VS for Yukon”版本中包含這些功能,但具體的發布日期還未確定。

在接下來的幾個月中,Microsoft 將發布有關這些功能的詳細資料,包括所有規範。歡迎廣大程式員和語言設計團體就這些功能以及任何其他感興趣的語言功能提出自己的看法和反饋。您可以將電子郵件發送到 mailto:sharp@microsoft.com,與 C# 語言設計人員取得聯絡。

更多資訊
C# Community Web 網站:http://www.csharp.net

Visual C#(tm) Product Web 網站:http://msdn.microsoft.com/vcsharp

  

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.