Effective C# 原則30:選擇與CLS相容的程式集(譯)

來源:互聯網
上載者:User

Effective C# 原則30:選擇與CLS相容的程式集
Item 30: Prefer CLS-Compliant Assemblies

.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相容,只用避免介面中的不相容結構就行了。通用語言的可操作性值得你花點時間。

===============================

   

Item 30: Prefer CLS-Compliant Assemblies
The .NET environment is language agnostic: Developers can incorporate components written in different .NET languages without limitations. In practice, it's almost true. You must create assemblies that are compliant with the Common Language Subsystem (CLS) to guarantee that developers writing programs in other languages can use your components.

CLS compliance is a new twist on that least common denominator approach to interoperability. The CLS specification is a subset of operations that every language must support. To create a CLS-compliant assembly, you must create an assembly whose public interface is limited to those features in the CLS specification. Then any language supporting the CLS specification must be capable of using the component. This does not mean you must limit your entire programming palette to the CLS-compliant subset of the C# language, however.

To create a CLS-compliant assembly, you must follow two rules. First, the type of all parameters and return values from public and protected members must be CLS compliant. Second, any non-CLS-compliant public or protected member must have a CLS-compliant synonym.

The first rule is simple to follow: You can have it enforced by the compiler. Add the CLSCompliant attribute to your assembly:

[ assembly: CLSCompliant( true ) ]

 

The compiler enforces CLS compliance for the entire assembly. If you write a public method or property that uses a construct that is not compliant with CLS, it's an error. That's good because it makes CLS compliance an easy goal. After turning on CLS compliance, these two definitions won't compile because unsigned integers are not compliant with 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 )
{
}

 

Remember that creating a CLS-compliant assembly affects only items that can be seen outside of the current assembly. Foo and Foo2 generate CLS compliance errors when declared either public or protected. However, if Foo and Foo2 were internal, or private, they could be included in a CLS-compliant assembly; CLS-compliant interfaces are required only for items that are exposed outside the assembly.

What about this property? Is it CLS compliant?

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

 

It depends. If MyClass is CLS compliant and indicates that it is CLS compliant, this property is CLS compliant. On the other hand, if MyClass is not marked as CLS compliant, this property is not CLS compliant. That means that the earlier TheProperty is CLS compliant only if MyClass resides in a CLS-compliant assembly.

You cannot build a CLS-compliant assembly if you have types in your public or protected interface that are not CLS compliant. If, as a component designer, you do not have an assembly marked as CLS compliant, you make it harder for users of your component to create CLS-compliant assemblies. They must hide your types and mirror the functionality in a CLS-compliant wrapper. Yes, this can be done. But, no, it's not a good way to treat the programmers who want to use your components. It's better to strive for CLS-compliant assemblies in all your work: This is the easiest way for clients to incorporate your work in their CLS-compliant assemblies.

The second rule is up to you: You need to make sure that you provide a language-agnostic way to perform all public and protected operations. You also need to make sure that you do not sneak a noncompliant object through your interface using polymorphism.

Operator overloading is a feature that some love and others hate. As such, not every language supports or allows operator overloading. The CLS standard does not take a pro or con stance on the concept of operator overloading. Instead, it defines a function name for each operator: op_equals is the function name created when you write an operator = function. op_addis the name for an overloaded addition operator. When you write an overloaded operator, the operator syntax can be used in languages that support overloaded operators. Developers using a language that does not support operator overloading must use the op_ function name. If you expect these programmers to use your CLS-compliant assembly, you should provide a more convenient syntax. That leads to this simple recommendation: Anytime you overload an operator, create a semantically equivalent function:

// 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 );
}

 

Finally, watch out for non-CLS types sneaking into an interface when you use polymorphic arguments. It's easy to do with event arguments. You can create a type that is not compliant with CLS and use it where a base type that is CLS-compliant is expected.

Suppose that you created this class derived from EventArgs:

internal class BadEventArgs : EventArgs
{
  internal UInt32 ErrorCode;
}

 

The BadEventArgs type is not CLS compliant; you should not use it with event handlers written in other languages. But polymorphism makes this easy to do. You can declare the event type to use the base class, 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 );

 

The interface declaration, which uses an EventArgs argument, is CLS compliant. However, the actual type you substituted in the event arguments was not. The end result is a type that some languages cannot use.

This discussion of CLS compliance ends with how CLS-compliant classes implement compliant or noncompliant interfaces. It can get complicated, but we'll simplify it. Understanding CLS compliance with interfaces also will help you fully understand what it means to be CLS compliant and how the environment views compliance.

This interface is CLS compliant if it is declared in a CLS-compliant assembly:

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

 

You can implement that interface in any CLS-compliant class. However, if you declare this interface in an assembly that is not marked as CLS compliant, the IFoo interface is not CLS compliant. In other words, an interface is CLS compliant only if it is defined in a CLS-compliant assembly; conforming to the CLS spec is not enough. The reason is compiler performance. The compilers check CLS compliance on types only when the assembly being compiled is marked as CLS compliant. Similarly, the compilers assume that types declared in assemblies that are not CLS compliant actually are not CLS compliant. However, the members of this interface have CLS-compliant signatures. Even if IFoo is not marked as CLS compliant, you can implement IFoo in a CLS-compliant class. Clients of this class could access DoStuff tHRough the class reference, but not through the IFoo reference.

Consider this small variation:

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

 

A class that publicly implements IFoo2 is not CLS compliant. To make a CLS-compliant class that implements IFoo2, you must use explicit interface implementation:

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 has a CLS-compliant public interface. Clients expecting the IFoo2 interface must access it through the non-CLS-compliant IFoo2 pointer.

Complicated? No, not really. Creating a CLS-compliant type mandates that your public and protected interfaces contain only CLS-compliant types. It means that your base class must be CLS compliant. All interfaces that you implement publicly must be CLS compliant. If you implement a non-CLS compliant interface, you must hide it from your public interface using explicit interface implementation.

CLS compliance does not force you to adopt a least common denominator approach to your designs and implementations. It means carefully watching the publicly accessible interfaces of your assembly. For any public or protected class, any type mentioned in these constructs must be CLS compliant:

Base classes

Return values for public and protected methods and properties

Parameters for public and protected methods and indexers

Runtime event arguments

Public interfaces, declared or implemented

The compiler tries to enforce a compliant assembly. That makes it easy for you to provide some minimum level of CLS support. With a bit of extra care, you can create an assembly that anyone using any language can use. The CLS specification tries to ensure that language interoperability is possible without sacrificing the constructs in your favorite language. You just need to provide alternatives in the interface.

CLS compliance requires you to spend a little time thinking about the public interfaces from the standpoint of other languages. You don't need to restrict all your code to CLS-compliant constructs; just avoid the noncompliant constructs in the interface. The payback of interlanguage operability is worth the extra time.
 
   

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在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.