從NullObject談C#6.0改進

來源:互聯網
上載者:User

標籤:

前言

         本文來聊一聊我們經常會做的空值檢查問題,從一個簡單的空值檢查Any Where,到設計模式的NullObjectPattern,再到C#6.0“可能”會提供的文法,讓我們體驗一次語言開發上的“持續改進”,Let’s go~

 

什麼是Null 參考異常

         作為一個敲過代碼的碼農來說,似乎沒有誰沒有遇到過NullReferenceException這個問題,有些時候當方法內部調用一個屬性、方法(委託)時,我們控制這些屬性在“外部”的表現(當然某些情況下使用ref關鍵字除外),所以我們要在方法的內部去判斷屬性、委託方法是否為Null來避免可能的、錯誤使用上帶來的Null 參考異常,這樣當我們知道如果對象為Null的話,我們會實現符合我們“預期”的行為。

 

解決Null 參考異常---Check Any Where

         這很簡單,我只要在需要用的地方檢查一下是否為Null就可以了。是的,這非常簡單,語義也很清晰,但是當你要重複檢查一個對象實體10000萬次時,你的代碼中將存在10000個如下程式碼片段:

        public void Check()        {            if (Person.AlivePerson() != null)            {                Person.AlivePerson().KeepAlive = true;            }         }

  你能容忍這樣的行為嗎?

         If(OK)

                   Continue;

         Else

                   Close;

 

應用NullObject設計模式

         NullObjectPattern出自forth by Gamma(設計模式4人組),核心內容是:提供一個給定對象的空值代理,空值代理中提供不做任何事情的方法實現。

         接下來讓我們看看維基百科上的C#實現:

// compile as Console Application, requires C# 3.0 or higherusing System;using System.Linq;namespace MyExtensionWithExample {    public static class StringExtensions {         public static int SafeGetLength(this string valueOrNull) {             return (valueOrNull ?? string.Empty).Length;         }    }    public static class Program {        // define some strings        static readonly string[] strings = new [] { "Mr X.", "Katrien Duck", null, "Q" };        // write the total length of all the strings in the array        public static void Main(string[] args) {            var query = from text in strings select text.SafeGetLength(); // no need to do any checks here            Console.WriteLine(query.Sum());        // The output will be: 18         }    }}

  在C#語言中,我們通過靜態擴充方法來實現將檢查方式統一在方法內部,而不是寫的到處都是,上面的例子中是在String類上實現了一個SafeGetLength擴充方法,將為所有String類型提供了一個方法,這樣我們在“代碼整潔”上又進了一步。

  下面我們再來看一個更常用的例子---來自於StackOverFlow

    public static class EventExtensions    {        public static void Raise<T>(this EventHandler<T> evnt, object sender, T args)            where T : EventArgs        {            if (evnt != null)            {                evnt(sender, args);            }        }    }

  最後,說一個細節問題,以上代碼均沒有實現“安全執行緒”,在大牛Eric Lippert的文章中針對安全執行緒有過一個更精彩的討論,請戳這裡。

  改進後的代碼時在方法內部增加了一個臨時變數,作為方法內部的拷貝,實現安全執行緒,如果有疑問請參考我的《C#堆vs棧》中對方法內部變數在堆棧上的表現一章。

    public class SomeClass    {        public event EventHandler<EventArgs> MyEvent;        private void DoSomething()        {            var tmp = MyEvent;            MyEvent.Raise(this, EventArgs.Empty);        }    }

 

更“潮”的方式-C#6.0文法

      來自MSDN Magazine的Mark Michaelis(《C#本質論》作者)給我們介紹了C#6.0在語言可能帶來的新改進,其中就有針對“Null條件運算子”的改進。

  C#6.0更多參考:

  Part One: https://msdn.microsoft.com/zh-cn/magazine/dn683793.aspx

  Part Two: https://msdn.microsoft.com/zh-cn/magazine/dn802602.aspx

 

  即使是 .NET 開發新手,也可能非常熟悉 NullReferenceException。有一個例外是幾乎總是會指出一個 Bug,因為開發人員在調用 (null) 對象的成員之前未進行充分的 null 檢查。請看看以下樣本:

public static string Truncate(string value, int length){  string result = value;  if (value != null) // Skip empty string check for elucidation  {    result = value.Substring(0, Math.Min(value.Length, length));  }  return result;}

  如果不進行 null 檢查,此方法會引發 NullReferenceException。儘管這很簡單,但檢查字串參數是否為 null 的過程卻稍微有些繁瑣。通常,考慮到比較的頻率,該繁瑣的方法可能沒有必要。C# 6.0 包括一個新的 null 條件運算子,可協助您更加簡便地編寫這些檢查:

public static string Truncate(string value, int length){            return value?.Substring(0, Math.Min(value.Length, length));}[TestMethod]public void Truncate_WithNull_ReturnsNull(){  Assert.AreEqual<string>(null, Truncate(null, 42));}

  根據 Truncate_WithNull_ReturnsNull 方法所示範的內容,如果對象的值實際上為 null,則 null 條件運算子將返回 null。這帶來了一個問題,即 null 條件運算子在調用鏈中出現時會是什麼情況?如以下樣本中所示:

public static string AdjustWidth(string value, int length){  return value?.Substring(0, Math.Min(value.Length, length)).PadRight(length);}[TestMethod]public void AdjustWidth_GivenInigoMontoya42_ReturnsInigoMontoyaExtended(){  Assert.AreEqual<int>(42, AdjustWidth("Inigo Montoya", 42).Length);}

  儘管 Substring 是通過 null 條件運算子進行調用的,並且 null value?.Substring 似乎返回了 null,但語言行為按您的想法進行。這簡化了對 PadRight 的調用過程,並立即返回 null,從而避免會導致出現 NullReferenceException 的編程錯誤。這個概念稱為“null 傳播”。

  Null 條件運算子會根據具體條件進行 null 檢查,然後再調用目標方法以及調用鏈中的所有其他方法。這將可能產生一個令人驚訝的結果,例如,text?.Length.GetType 語句中的結果。

如果 null 條件運算子在調用目標為 null 時返回 null,那麼調用會傳回值類型的成員時最終會是什麼資料類型(假定實值型別不能為 null)?例如,從 value?.Length 返回的資料類型不能只是 int。答案當然是:可為 Null 的型別(int?)。實際上,嘗試僅將結果分配給 int 將會出現編譯錯誤:

int length = text?.Length; // Compile Error: Cannot implicitly convert type ‘int?‘ to ‘int‘

  Null 條件具有兩種文法形式。首先,問號在點運算子前面 (?.)。其次,將問號和索引運算子結合使用。例如,給定一個集合(而非在索引到集合之前顯式進行 null 檢查),您就可以使用 null 條件運算子執行此操作:

public static IEnumerable<T> GetValueTypeItems<T>(  IList<T> collection, params int[] indexes)  where T : struct{  foreach (int index in indexes)  {    T? item = collection?[index];    if (item != null) yield return (T)item;  }}

  此樣本使用了運算子 ?[…] 的 null 條件索引形式,導致僅在集合不為 null 時才索引到集合。通過 null 條件運算子的此形式,T? item = collection?[index] 語句在行為上相當於:

T? item = (collection != null) ? collection[index] : null.

請注意,null 條件運算子僅可檢索項目,不會分配項目。如果給定 null 集合,那麼這意味著什嗎?

請注意針對參考型別使用 ?[…] 時的隱式歧義。由於參考型別可以為 null,因此對於集合是否為 null,或者是否元素本身實際上就是 null 而言,來自 ?[…] 運算子的 null 結果不明確。

Null 條件運算子的一個非常有用的應用程式解決了 C# 自 C# 1.0 以來一直存在的的一個特性,即在調用委託之前檢查是否為 null。我們來看一下中顯示的 C# 2.0 代碼。

  圖 1 在調用委託之前檢查是否為 Null

class Theremostat{  event EventHandler<float> OnTemperatureChanged;  private int _Temperature;  public int Temperature  {    get    {      return _Temperature;    }    set    {      // If there are any subscribers, then      // notify them of changes in temperature      EventHandler<float> localOnChanged =        OnTemperatureChanged;      if (localOnChanged != null)      {        _Temperature = value;        // Call subscribers        localOnChanged(this, value);      }    }  }}

  通過使用 null 條件運算子,整個 set 實現過程就可簡化為:

OnTemperatureChanged?.Invoke(this, value)

  現在,您只需對將 null 條件運算子作為首碼的 Invoke 進行調用,不再需要將委託執行個體分配給本地變數,從而實現安全執行緒,甚至是在調用委託之前顯式檢查值是否為 null。

C# 開發人員都很想知道在最新的四個版本中是否對此內容有所改進。答案是最終進行了改進。僅此一項功能就可以改變調用委託的方式。

另一個 null 條件運算子普及的常見模式是與 coalesce 運算子結合使用。您無需在調用 Length 之前對 linesOfCode 進行 null 檢查,而是可以編寫項目計數演算法,如下所示:

List<string> linesOfCode = ParseSourceCodeFile("Program.cs");return linesOfCode?.Count ?? 0;

在這種情況下,任何空集合(無項目)和 null 集合均標準化為返回相同數量。總之,null 條件運算子將實現以下功能:

  1.  如果運算元為 null,則返回 null

  2.  如果運算元為 null,則簡化調用鏈中的其他調用

  3.  如果目標成員返回一個實值型別,則返回可為 Null 的型別 (System.Nullable<T>)。

  4.  以安全執行緒的方式支援委託調用

  5.  可用作成員運算子 (?.) 和索引運算子 (?[…])

 

 範例程式碼下載

 

引用

http://stackoverflow.com/questions/13629051/net-event-raising-and-nullobject-pattern ---安全執行緒的擴充機制

https://msdn.microsoft.com/zh-cn/magazine/dn802602.aspx ---C#6.0 Null條件運算子

http://en.wikipedia.org/wiki/Null_Object_pattern ---維基百科上的NullObjectPattern解釋

 

從NullObject談C#6.0改進

相關文章

聯繫我們

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