天天看點

Effective C#: 3.盡量使自定義的類型與公共語言規範兼

<script type="text/javascript"> </script> <script type="text/javascript" src="http://pagead2.googlesyndication.com/pagead/show_ads.js"> </script> <script type="text/javascript"> </script><script type="text/javascript" src="http://pagead2.googlesyndication.com/pagead/show_ads.js"> </script>

Effective C#: 3.盡量使自定義的類型與公共語言規範相容

         陳銘        Microsoft C#/.NET Asia MVP

  難度:5/10                       條款2

一個陽光明媚的早上,你精神百倍的坐到自己的電腦前面,準備開始一天繁忙的工作。今天的工作内容是編寫一個根據使用者的訂單自動發送确認郵件的程式——毫無疑問,工具是你最拿手的C#。

這時候你突然想到你的一位執著于Visual Basic.NET的同僚曾經編寫過一個用于發送電子郵件的小元件,為什麼不直接使用現成的元件來簡化手頭上的工作呢?不是說.NET的優點之一就是簡便快速的跨語言元件重用嗎?看上去是個相當不錯的主意。

拿到這個元件和相關的文檔并不費什麼周折,但是調用文檔上的一段文字卻讓你如墜五裡霧中:“……設定郵件位址之後,調用Mail類對象的Mail方法發送郵件……”。似乎函數的名字用Send更好一些,這也還罷了。Mail類的Mail方法,難道是構造函數?怎麼可能呢?或者是文檔寫錯了?

 困惑無濟于事,不如先來看看Visual Basic.NET的一些文法吧:在VB.NET中當然也可以定義類、定義類的構造函數,但其文法卻和C#大相徑庭——VB.NET不是用類名稱,而是用關鍵字New來定義構造函數的,如下所示:

  ‘VB.NET definition for class Mail

  Public Class Mail

       Sub New()

            ‘Here is the constructor!

       End Sub

       ‘ Other type members

       Sub Mail()

            ‘This is only a member function

            ‘Not class constructor

       End Sub

  End Class

由于使用了New來定義構造函數,VB.NET就可以定義一個和類型同名的成員函數,而這在C#裡是絕不可能做到的。在編譯之後,構造函數在類型中繼資料(Metadata)中的名稱為.ctor,這個名稱顯然和類型名并不沖突。是以,成員函數名不能與類型名稱相同應該不是.NET平台的限制而是C#文法上的限制。

那麼.NET平台上的跨語言互動和重用有從何談起呢?在了解.NET對跨語言互動重用的支援之前,有必先了解以下的兩個概念:

公共類型系統 (CTS, Common Type System)

公共語言規範 (CLS, Common Language Specification)

.NET建構在一個豐富而龐大的類型系統之上,這個類型系統的設計目的之一就是盡可能多的支援現有的各種程式語言的特性,從過程式語言到面向對象語言,甚至包括了諸如LISP的函數式語言(如果你從來沒有接觸過函數式語言,那麼.NET支援的TailCall函數調用方式可能會令你吃驚不小)。微軟為這個作為.NET建築根基的類型系統定名為公共類型系統,即CTS。

由于CTS具有如此完備的定義,建構于其上的.NET——更具體地說是.NET的“彙編語言”MSIL——具有非常強大的表達能力,例如:使用MSIL可以在類集中定義不屬于任何類的全局函數和變量(你沒有看錯,.NET确實支援全局變量),支援僅有傳回值類型不同的函數重載(可以在條款X中找到這種重載的應用執行個體),甚至支援為接口定義靜态成員變量和方法。

進而,正是MSIL豐富的表達能力,才使得.NET能夠海納百川般的将各式各樣的程式設計語言納入.NET架構之中。從為.NET量身定做的C#,改頭換面之後的C++(Managed C++ Extension)、Visual Basic(VB.NET)到Eiffel,甚至是像Jscript.NET和Python這樣的腳本語言,.NET平台目前支援的程式語言已經有十幾種之多,而且仍然不斷的有新的成員加入到這個群體中來。不需要經曆漫長而陡峭的學習曲線就可以在全新的.NET平台上應用自己熟知的程式語言進行應用程式開發,這對于現實中的程式員來說無疑是一個莫大的福音。

但是,至此我們還隻能說“.NET是一個支援多種語言開發的環境”,不同語言之間的互動仍然無從談起。雖然采用了統一的類型資訊存儲形式和中間代碼,但是不同的語言表達能力不盡相同,支援的文法也各異。這就使得不同的.NET程式設計語言暴露出的CTS的功能子集不完全相同。比如說,C#支援的運算符重載在VB.NET中沒有直接對應的調用方法、而函數式語言的所謂TailCall函數調用方式更是不可能被其它.NET語言所支援。

為此,.NET又在CTS的基礎上定義了

公共語言規範 (CLS, Common Language Specification)

與CTS不同,CLS并不追求功能的完備性,而是着重規定了各種.NET語言在功能上必須實作的CTS的一個子集,以及如何在程式中正确的使用這個集合内的功能。由于CLS的出發點是不同語言之間的互動性,是以它隻适用于從類集外部可通路的類型及其成員,例如類集中聲明為public的類型及其聲明為public/protected的成員變量和成員函數。隻要確定這些從類集以外可以通路的類型及其成員符合CLS規範,就能夠保證所設計的類庫能夠在幾乎所有.NET程式語言中毫無困難的使用,進而實作.NET的跨語言互動能力。至于其它私有類型及其成員,則可以充分發揮特定語言所特有的表達能力,物盡其用,以求最大限度的簡化程式設計工作。比如說VB.NET直接支援COM對象的晚綁定(late binding),而在諸如C#的其它.NET語言當中實作同樣的功能并不容易,那麼就可以考慮由VB.NET編寫進行COM對象操作的類集,用C#實作應用的其它部分,隻要它們所暴露的界面符合CLS的規定,各子產品之間就可以輕而易舉的實作無縫內建。

完整的CLS規範非常瑣碎細緻的規定了.NET程式設計的方方面面(幸好,并不是必須記住這些條款才能確定你的程式符合CLS規範,編譯器可以完成大部分的檢查工作,稍後會有更詳細的介紹)。下表羅列了CLS的部分規則,熟悉這些較為常用的規則将會有助于更明确的了解CLS的意義:

關于命名

字元和大小寫

不能僅憑大小寫的不同來區分兩個辨別

辨別的唯一性

除重載以外,同一名稱解析空間中不能有

任意兩個同名的辨別

函數聲明

函數聲明中用到的參數和傳回值類型必須與CLS相容

關于類型

内建類型

.NET内建類型中,僅有Byte、Int16、Int32、Int64、Single、Double、Boolean、Char、Decimal、IntPtr和String是CLS相容的*

閉合特性

與CLS相容的接口和抽象基類的所有成員必須保證與CLS相容

構造函數調用

在通路任何類成員之前,子類的構造函數必須先調用父類的構造函數

類型成員

重載

函數、屬性和事件不能僅憑傳回值類型的不同進行重載

* 這些類型在C#裡分别對應于:byte, short, int, long, float, double, bool, char, decimal和string

 這裡羅列的僅是CLS規範的極小的一個部分。要求程式員記住這些繁瑣的規則條款似乎有些不切實際,是以,各種.NET語言的編譯器均提供了CLS相容性檢測的功能。隻要你為那些希望與CLS規範相容的類集添加上CLSCompliantAttribute特性,編譯器就會幫助你檢查代碼是否符合CLS規範,并且給出必要的錯誤資訊。

例如,你可以嘗試編譯下面的C#代碼:

//clstest.cs

using System;

[assembly:CLSCompliant(true)]

namespace Effective.Csharp.Chapter3 {

  public class MyClass {

       //uint類型不與CLS相容

       public uint GetMyData() {

            //… 函數的具體實作

}

  }

}

編譯器就會産生出如下的錯誤資訊:

clstest.cs(7,10): error CS3002: Return type of

'Effective.Csharp.Chapter3.MyClass.GetMyData()' is not CLS-compliant

而如果将GetMyData函數的通路設定改成private或者internal,編譯器就不會再報類似的錯誤了。

這樣,有了編譯器的幫助,確定你所設計的類型與CLS相容就變得輕而易舉了。但是,有些類庫最初就未能按照CLS相容的标準設計(就向本章起始提到的那個例子),而你僅僅是這個類庫的使用者,當這些類庫觸及不同語言功能互動的死角的時候,我們不就無能為力了嗎?其實不然,.NET提供了異常強大的類型反射(Reflection)功能,可以用于通路那些程式設計語言力所不能及的類型及其成員,例如針對本章開頭提到的例子,可以使用類型反射來調用Mail方法:

//…use reflection to invoke Mail.Mail

Mail mail = new Mail(“[email protected]”);

//… initialize mail object

Type t = typeof(Mail);

//invoke Mail method

t.InvokeMember(“Mail”, BindingFlags.InvokeMethod,

       null, mail, new object[] {} );

顯而易見,類型反射的功能雖然強大,但是使用類型反射的代碼的性能和可讀性會大大降低。是以,作為元件的設計者,應該盡量確定元件定義的類型與CLS相容。(完)

      * 本文系原創作品,未經作者本人許可請勿轉載。   <script type="text/javascript"> </script> <script type="text/javascript" src="http://pagead2.googlesyndication.com/pagead/show_ads.js"> </script> <script type="text/javascript"> </script><script type="text/javascript" src="http://pagead2.googlesyndication.com/pagead/show_ads.js"> </script>

繼續閱讀