程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> .NET網頁編程 >> C# >> 關於C# >> Effective C#原則30:選擇與CLS兼容的程序集

Effective C#原則30:選擇與CLS兼容的程序集

編輯:關於C#

.Net運行環境是語言無關的:開發者可以用不同的.Net語言編寫組件。而且在實際開發中往往就是這樣的。你創建的程序集必須是與公共語言系統(CLS)是兼容的,這樣才能保證其它的開發人員可以用其它的語言來使用你的組件。

CLS的兼容至少在公共命名上要與互用性靠近。CLS規范是一個所有語言都必須支持的最小操作子集。創建一個CLS兼容的程序集,就是說你創建的程序集的公共接口必須受CLS規范的限制。這樣其它任何滿足CLS規范的語言都可以使用這個組件。然而,這並不是說你的整個程序都要與CLS的C#語言子集相兼容。

為了創建CLS兼容的程序集,你必須遵從兩個規則:首先,所以參數以及從公共的和受保護的成員上反回的值都必須是與CLS兼容的。其次,其它不與CLS兼容的公共或者受保護成員必須存在CLS兼容的同意對象。

第一個規則很容易實現:你可以讓編譯來強制完成。添加一個CLSCompliant 特性到程序集上就行了:

[ assembly: CLSCompliant( true ) ]

編譯器會強制整個程序集都是CLS兼容的。如果你編寫了一個公共方法或者屬性,它使用了一個與CLS不兼容的結構,那麼編譯器會認為這是錯誤的。這非常不錯,因為它讓CLS兼容成了一個簡單的任務。在打開與CLS兼容性後,下面兩個定義將不能通過編譯,因為無符號整型不與CLS兼容:

// Not CLS Compliant, returns unsigned int:
public UInt32 Foo( )
{
 return _foo;
}
// Not CLS compliant, parameter is an unsigned int.
public void Foo2( UInt32 parm )
{
}

記住,創建與CLS兼容的程序集時,只對那些可以在當前程序集外面可以訪問的內容有效。Foo 和Foo2 在定義為公共或者受保護時,會因與CSL不兼容而產生錯誤。然而如果Foo 和Foo2是內部的,或者是私有的,那麼它們就不會被包含在要與CLS兼容的程序集中;CLS兼容接口只有在把內容向外部暴露時才是必須的。

那麼屬性又會怎樣呢?它們與CLS是兼容的嗎?

public MyClass TheProperty
{
 get { return _myClassVar; }
 set { _myClassVar = value; }
}

這要視情況而定,如果MyClass是CLS兼容的,而且表明了它是與CLS兼容的,那麼這個屬性也是與CLS兼容的。相反,如果MyClass沒有標記為與CLS兼容,那麼屬性也是與CLS不兼容的。就意味著前面的TheProperty屬性只有在MyClass是在與CLS兼容的程序集中是,它才是與CLS兼容的。

如果你的公共的或者受保護的接口與CLS是不兼容的,那麼你就不能編譯成CLS兼容的程序集。作為一個組件的設計者,如果你沒有給程序集標記為CLS兼容的,那麼對於你的用戶來說,就很難創建與CLS兼容的程序集了。他們必須隱藏你的類型,然後在CLS兼容中進行封裝處理。確實,這樣可以完成任務,但對於那些使用組件的程序員來說不是一個好方法。最好還是你來努力完成所有的工作,讓程序與CLS兼容:對於用戶為說,這是可以讓他們的程序與CLS兼容的最簡單的方法。

第二個規則是取決與你自己的:你必須確保所有公共的及受保護的操作是語言無關的。同時你還要保證你所使用的多態接口中沒有隱藏不兼容的對象。

操作符重載這個功能,有人喜歡有人不喜歡。同樣,也並不是所有的語言都支持操作符重載的。CLS標准對於重載操作符這一概念即沒有正面的支持也沒有反正的否定。取而代之是,它為每個操作符定義了一了函數:op_equals就是=操作符所對應的函數名。op_addis是重載了加號後的函數名。當你重載了操作符以後,操作符語法就可以在支持操作符重載的語言中使用。如果某些開發人員使用的語言不支持操作符重載時,他們就必須使用op_這樣的函數名了。如果你希望那些程序員使用你的CLS兼容程序集,你應該創建更多的方便的語法。介此,推薦一個簡單的方法:任何時候,只要重載操作運算符時,再提供一個等效的函數:

// Overloaded Addition operator, preferred C# syntax:
public static Foo operator+( Foo left, Foo right)
{
 // Use the same implementation as the Add method:
 return Foo.Add( left, right );
}
// Static function, desirable for some languages:
public static Foo Add( Foo left, Foo right)
{
 return new Foo ( left.Bar + right.Bar );
}

最後,注意在使用多態的接口時,那些非CLS的類型可能隱藏在一些接口中。最容易出現的就是在事件的參數中。這會讓你創建一些CLS不兼容的類型,而在使用的地方卻是用與CLS兼容的基類。

假設你創建了一個從EventArgs派生的類:

internal class BadEventArgs : EventArgs
{
 internal UInt32 ErrorCode;
}

這個BadEventArgs類型就是與CLS不兼容的,你不可能在其它語言中寫的事件句柄上使用這個參數。但多態性卻讓這很容易發生。你只是申明了事件參數為基類:EventArgs:

// Hiding the non-compliant event argument:
public delegate void MyEventHandler(
 object sender, EventArgs args );
public event MyEventHandler OnStuffHappens;
// Code to raise Event:
BadEventArgs arg = new BadEventArgs( );
arg.ErrorCode = 24;
// Interface is legal, runtime type is not:
OnStuffHappens( this, arg );

以EventArgs為參數的接口申明是與CLS兼容的,然而,實際取代參數的類型是與CLS不兼容的。結果就是一些語言不能使用。

最後以如何實現CLS兼容類或者不兼容接口來結束對CLS兼容性的討論。兼容性是可以實現的,但我們可以更簡單的實現它。明白CLS與接口的兼容同樣可以幫助你完整的理解CLS兼容的意思,而且可以知道運行環境是怎樣看待兼容的。

這個接口如果是定義在CLS兼容程序集中,那麼它是CLS兼容的:

[ assembly:CLSCompliant( true ) ]
public interface IFoo
{
 void DoStuff( Int32 arg1, string arg2 );
}

你可以在任何與CLS兼容的類中實現它。然而,如果你在與沒有標記與CLS兼容的程序集中定義了這個接口,那麼這個IFoo接口就並不是CLS兼容的接口。也就是說,一個接口只是滿足CLS規范是不夠的,還必須定義在一個CSL兼容的程序集中時才是CLS兼容的。原因是編譯器造成的,編譯器只在程序集標記為CLS兼容時才檢測CLS兼容類型。相似的,編譯器總是假設在CLS不兼容的程序集中定義的類型實際上都是CLS不兼容的。然而,這個接口的成員具有CLS兼容性標記。即使IFoo沒有標記為CLS兼容,你也可以在CLS兼容類中實現這個IFoo接口。這個類的客戶可以通過類的引來訪問DoStuff,而不是IFoo接口的引用。

考慮這個簡單的參數:

public interface IFoo2
{
 // Non-CLS compliant, Unsigned int
 void DoStuff( UInt32 arg1, string arg2 );
}

一個公開實現了IFoo2接口的類,與CLS是不兼容的。為了讓一個類即實現IFoo2接口,同時也是CLS兼容的,你必須使用清楚的接口定義:

public class MyClass: IFoo2
{
 // explicit interface implementation.
 // DoStuff() is not part of MyClass's public interface
 void IFoo2.DoStuff( UInt32 arg1, string arg2 )
 {
  // content elided.
 }
}

MyClass 有一個與CLS兼容的接口,希望訪問IFoo2 接口的客戶必須通過訪問與CLS不兼容的IFoo2接口指針。

兼容了嗎?不,還沒。創建一個CLS兼容類型要求所有的公共以及受保護接口都只包含CLS兼容類型。這就是說,某個類的基類也必須是CLS兼容的。所有實現的接口也必須是CLS兼容的。如果你實現了一個CLS不兼容的接口,你必須實現明確的接口定義,從而在公共接口上隱藏它。

CLS兼容性並沒有強迫你去使用最小的公共名稱來實現你的設計。它只是告訴你應該小心使用程序集上的公共的接口。以於任何公共的或者受保護的類,在構造函數中涉及的任何類型必須是CLS兼容的,這包含:

*基類

*從公共或者受保護的方法和屬性上返回的值

*公共及受保護的方法和索引器的參數

*運行時事件參數

*公共接口的申明和實現

編譯器會試圖強制兼容一個程序集。這會讓提供最小級別上的CLS兼容變得很簡單。再稍加小心,你就可以創建一個其它語言都可以使用的程序集了。編譯器的規范試圖確保不用犧牲你所喜歡的語言的結構就可以盡可能的與其它語言兼容。你只用在接口中提供可選的方案就行了。

CLS兼容性要求你花點時間站在其它語言上來考慮一下公共接口。你不必限制所有的代碼都與CLS兼容,只用避免接口中的不兼容結構就行了。通用語言的可操作性值得你花點時間。

返回教程目錄

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved