Effective C# 原則8:確保0對於實值型別資料是有效(翻譯)

來源:互聯網
上載者:User

Effective C# 原則8:確保0對於實值型別資料是有效
Ensure That 0 Is a Valid State for Value Types

.Net系統預設所有的對象初始化時都為0。這並沒有提供一個方法來預防其他程式員建立的實值型別資料的執行個體在初始化是都是0。請讓你的資料類型預設值也是0。

一個特殊情況是在枚舉類型資料中。決不要建立一個不包括0在內的枚舉類型。所有的枚舉類型都是從System.ValueType派生的。枚舉類型的值是從0開始的,但你可以改變這一行為:

public enum Planet
{
  // Explicitly assign values.
  // Default starts at 0 otherwise.
  Mercury = 1,
  Venus = 2,
  Earth = 3,
  Mars = 4,
  Jupiter = 5,
  Saturn = 6,
  Neptune = 7,
  Uranus = 8,
  Pluto = 9
}

Planet sphere = new Planet();

sphere此時的值就是0,而這並不是一個有效值。枚舉類型的取值限制在所有列舉的值中,任何依懶這一(普通)事實的代碼都將無法工作。當你為你的枚舉類型建立你自己的取值時,請確保0是當中的一個。如果你的枚舉類型採用的是以位(bit)模式,把0定義為其它屬性不存在時的取值。

按照現在的情況,你迫使使用者必須精確的初始化值:

Planet sphere = Planet.Mars;

這將使包含(Planet)這一類型的其它類型很難建立:
public struct ObservationData
{
  Planet   _whichPlanet; //what am I looking at?
  Double  _magnitude; // perceived brightness.
}

建立一個新ObservationData執行個體的使用者會建立一個不合法的Planet成員:

ObservationData d = new ObservationData();

最後建立的ObservationData的成員_magnitude的值是0,這是合理的。但_whichPlanet卻是無效的。你須要讓0也是有效(狀態)。如果可能,選擇把0做為一個最好的預設。Planet枚舉類型沒有一個明確的預設值,無論使用者是否任意的選擇一些行星,這都不會給人留下好的感覺。當你陷入這樣的情況時,使用0做為一個非初始化的值,這也是在後面可以更新的:

public enum Planet
{
  None = 0,
  Mercury = 1,
  Venus = 2,
  Earth = 3,
  Mars = 4,
  Jupiter = 5,
  Saturn = 6,
  Neptune = 7,
  Uranus = 8,
  Pluto = 9
}

Planet sphere = new Planet();

此時,sphere具有一個(預設)值None。為Planet枚舉類型添加的這個非初始化的預設值,對ObservationData結構。最新建立的ObservationData對象的目標上具有None和一個數值0。添加一個清晰的建構函式讓使用者為你的類型的所有欄位明白的初始化:

public struct ObservationData
{
  Planet   _whichPlanet; //what am I looking at?
  Double  _magnitude; // perceived brightness.

  ObservationData( Planet target,
    Double mag )
  {
    _whichPlanet = target;
    _magnitude = mag;
  }
}

但請記住,預設的建構函式還是可訪問的,而且是結構的部份。使用者還是可以建立一個系統初始化的變數,而你無法阻止它。

在結束枚舉類型轉而討論其它類型之前,你須要明白幾個用於標記的特殊枚舉類型規則。枚舉類型在使用Flags特性時,必須把None的值設定為0:

[Flags]
public enum Styles
{
  None = 0,
  Flat = 1,
  Sunken = 2,
  Raised = 4,
}

很多開發人員使用枚舉標記和位元運算操作AND進行運行,0值會與位標記產生嚴重的問題。下面這個實驗如果Flat的值是0時,是決不會成功的:

if ( ( flag & Styles.Flat ) != 0 ) // Never true if Flat == 0.
  DoFlatThings( );

如果你遇到Flags,確保0對它來說是有效,並且這就著:“對所有缺少的標記。”

另一個很常見的初始化問題就是實值型別中包含了參考型別。字串是一個常見的例子:

public struct LogMessage
{
  private int _ErrLevel;
  private string _msg;
}

LogMessage MyMessage = new LogMessage( );

MyMessage包含了一個_msg為null的引用欄位。這裡沒有辦法強行使用另一個不同的初始化方法,但你利用屬性來局部化這個問題。你建立一個屬性向所用的使用者暴露_Msg的值。添加一個商務邏輯,使得當字串為null引用是,用空 串來取而代之:

public struct LogMessage
{
  private int _ErrLevel;
  private string _msg;

  public string Message
  {
    get
    {
      return (_msg != null ) ?
        _msg : string.Empty;
    }
    set
    {
      _msg = value;
    }
  }
}

(譯註:我個人覺得這裡違反了原則一。當對兩個執行個體進行賦值COPY時,會出現,你明明使用了a=b的運行,但實際上a!=b的結果。可以參見原則1。)

在你自己的資料類型內部,你應該添加這樣的一個屬性。做了這樣的局部處理後,null引用在某一位置做了驗證。當調用是在你的程式集內時,Message的訪問器基本上是可以很好的內聯的。你將會取得高效低錯的代碼。

系統為所有的實值型別資料初始化為0,而沒有辦法防止使用者在建立一個實值型別執行個體時,給所有的實值型別都賦值為0。如果可能,把0設定為自然的預設值。特殊情況下,使用Flags特性的枚舉類型必須確保0是所有預設標記的值。

========================================================
Item 8: Ensure That 0 Is a Valid State for Value Types
The default .NET system initialization sets all objects to all 0s. There is no way for you to prevent other programmers from creating an instance of a value type that is initialized to all 0s. Make that the default value for your type.

One special case is enums. Never create an enum that does not include 0 as a valid choice. All enums are derived from System.ValueType. The values for the enumeration start at 0, but you can modify that behavior:

public enum Planet
{
  // Explicitly assign values.
  // Default starts at 0 otherwise.
  Mercury = 1,
  Venus = 2,
  Earth = 3,
  Mars = 4,
  Jupiter = 5,
  Saturn = 6,
  Neptune = 7,
  Uranus = 8,
  Pluto = 9
}

Planet sphere = new Planet();

 

sphere is 0, which is not a valid value. Any code that relies on the (normal) fact that enums are restricted to the defined set of enumerated values won't work. When you create your own values for an enum, make sure that 0 is one of them. If you use bit patterns in your enum, define 0 to be the absence of all the other properties.

As it stands now, you force all users to explicitly initialize the value:

Planet sphere = Planet.Mars;

 

That makes it harder to build other value types that contain this type:

public struct ObservationData
{
  Planet   _whichPlanet; //what am I looking at?
  Double  _magnitude; // perceived brightness.
}

 

Users who create a new ObservationData object will create an invalid Planet field:

ObservationData d = new ObservationData();

 

The newly created ObservationData has a 0 magnitude, which is reasonable. But the planet is invalid. You need to make 0 a valid state. If possible, pick the best default as the value 0. The Planet enum does not have an obvious default. It doesn't make any sense to pick some arbitrary planet whenever the user does not. When you run into that situation, use the 0 case for an uninitialized value that can be updated later:

public enum Planet
{
  None = 0,
  Mercury = 1,
  Venus = 2,
  Earth = 3,
  Mars = 4,
  Jupiter = 5,
  Saturn = 6,
  Neptune = 7,
  Uranus = 8,
  Pluto = 9
}

Planet sphere = new Planet();

 

sphere now contains a value for None. Adding this uninitialized default to the Planet enum ripples up to the ObservationData structure. Newly created ObservationData objects have a 0 magnitude and None for the target. Add an explicit constructor to let users of your type initialize all the fields explicitly:

public struct ObservationData
{
  Planet   _whichPlanet; //what am I looking at?
  Double  _magnitude; // perceived brightness.

  ObservationData( Planet target,
    Double mag )
  {
    _whichPlanet = target;
    _magnitude = mag;
  }
}

 

But remember that the default constructor is still visible and part of the structure. Users can still create the system-initialized variant, and you can't stop them.

Before leaving enums to discuss other value types, you need to understand a few special rules for enums used as flags. Enums that use the Flags attribute should always set the None value to 0:

[Flags]
public enum Styles
{
  None = 0,
  Flat = 1,
  Sunken = 2,
  Raised = 4,
}

 

Many developers use flags enumerations with the bitwise AND operator. 0 values cause serious problems with bitflags. The following test will never work if Flat has the value of 0:

if ( ( flag & Styles.Flat ) != 0 ) // Never true if Flat == 0.
  DoFlatThings( );

 

If you use Flags, ensure that 0 is valid and that it means "the absence of all flags."

Another common initialization problem involves valuetypes that contain references. Strings are a common example:

public struct LogMessage
{
  private int _ErrLevel;
  private string _msg;
}

LogMessage MyMessage = new LogMessage( );

 

MyMessage contains a null reference in its _Msg field. There is no way to force a different initialization, but you can localize the problem using properties. You created a property to export the value of _Msg to all your clients. Add logic to that property to return the empty string instead of null:

public struct LogMessage
{
  private int _ErrLevel;
  private string _msg;

  public string Message
  {
    get
    {
      return (_msg != null ) ?
        _msg : string.Empty;
    }
    set
    {
      _msg = value;
    }
  }
}

You should use this property inside your own type. Doing so localizes the null reference check to one location. The Message accessor is almost certainly inlined as well, when called from inside your assembly. You'll get efficient code and minimize errors.

The system initializes all instances of value typess to 0. There is no way to prevent users from creating instances of value types that are all 0s. If possible, make the all 0 case the natural default. As a special case, enums used as flags should ensure that 0 is the absence of all flags.

 

相關文章

聯繫我們

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