[Effective C#]第一章-C#語言慣用語法

  • 2165
  • 0

整理一下最近看的Effective C#這本書的第一章 - C#語言慣用語法 的心得

Item 01:偏好隱含型別的區域變數

C#語言加入var的宣告,一開始的目的是為了支援匿名型別。在微軟的文件 MSDN | 隱含類型區域變數中有寫到

var 關鍵字不代表 variant,也不代表變數是不嚴格規定類型或晚期繫結的。 只代表編譯器會判斷並指派最適當的類型。

Effective C# 建議偏好使用var的原因有兩個:

  • 編譯器會判斷並指派最適當的類型,避免因為挑錯宣告型別,造成編譯器本來能夠避免的低效率
  • 它讓開發者注意重要的部分-變數名稱語意的意涵,而不是變數的型別

當然,會說偏好而不是總是是有原因的,因為我們還是有需要明確指定型別的時機。例如型別的轉換,從longint,涉及精度的損失。透過明確的宣告,可以幫助編譯器對有危險的轉換做出警告。

Effective C# 建議var的使用時機為:

明確宣告所有數值型別(int、float、double與其他),其他都使用var

參考

Item 02:偏好readonly而非const

C#語言提供兩種不同的常數﹕編譯期常數(使用const宣告)與執行期常數(使用readonly宣告)。他們有不同的行為,用錯了會有負面影響。

余小章@大內殿堂 | 定義常數時用 readonly 好? 還是 const 好?有簡單清楚的例子說明這兩者的差異。

結論如下:

我們在定義常數的時候會建議使用readonly而不是const,雖然說使用const的效能比使用static readonly效能好一些,但是整體的靈活性及方便性都是static readonly勝出的。

Effective C# 建議也相同:

必須在編譯期確定的值必須使用const:屬性參數、switch case標籤與enum定義,以及少數不會在版本間變化的值。其餘狀況則傾向使用readonly常數以提升彈性。

Item 03:偏好is或as運算子而非型別轉換

進行型別轉換時,相較於型別轉換的方式,isas運算子沒有try/catch子句。而且,型別轉換的方式除了要捕捉例外之外,還需要檢查null,因為null可使用型別轉換成為任何參考型別。

使用isas運算子代表更清楚直接的程式碼語意,因為它們只是單純的檢查被轉換物件的執行期的型別,除了必要的boxing,它們不執行其他的操作。型別轉換的方式可使用轉換運算子將物件轉成所要求的型別,但有可能因為對型別轉換的觀念不夠清楚,在某些狀況下,誤判了型別轉換的結果。

Effective C# 建議

雖然物件導向Best Practice要求你避開型別轉換,但有時候沒得閃避。如果你無法避免轉換,使用語言的isas運算子以清楚的表達你的意圖。

參考

Item 04:以內插字串取代string.Format()

之前C#語言在處理字串與變數的連接時,是使用String.Format(...)這種方式。但這種語法其實不直覺,因為還需要計算參數的位置。C# 6提供了Interpolated Strings的功能,寫法真的乾淨清楚又好用。

Effective C# 建議

新的Interpolated Strings語法比較容易正確使用。它更有威力,且容易利用新式發開中常見的技術。

參考

Item 05:String有多國語系的需求時,使用FormattableString

很直覺,直接參考 .NET 學習紀錄-Item 5 | Prefer FormattableString for Culture-Specific Strings

Effective C# 建議

Interpolated Strings支援所有你所需的國際化與特定區域化。當你需要特定文化時,你必須明確地告訴Interpolated Strings要建構FormattableString,然後可以使用你指定的文化將它轉換成字串。

Item 06:避免用字串型別API(Avoid String-ly Typed APIS)

一開始不了解標題的意思,理解內容之後,知道是以C# 6.0新增的nameof關鍵字來取得變數名稱,以取代使用字串寫死的變數名稱。

使用nameof取代字串變數名稱附帶的好處是,程式碼靜態分析等自動化工具可以更容易找出甚至修改各種錯誤。

參考

Item 07:以delegate表示callback(Express Callbacks with Delegates)

在事件處理以及LINQ中,大量的使用了callback。覺得這本電子書-從ES6開始的JavaScript學習生活中將callback解釋得很明白:

延續傳遞風格(Continuation-passing style, CPS),它的對比是"直接風格(Direct style)",這兩種是程式開發時所使用的風格,CPS早在1970年代就已經被提出來了。CPS用的是明確地移轉控制權到下一個函式中,也就是使用"延續函式"的方式,一般稱它為"回調函式"或"回調(Callback)"。回調是一個可以作為傳入參數的函式,用於在目前的函式呼叫執行最後移交控制權,而不使用函式回傳值的方式。

C#使用delegate讓我們宣告callback的傳入即傳出值,並且隨著C#版本的演進,出現更直覺方便的寫法-MSDN | 如何:宣告、產生和使用委派(C# 程式設計手冊)。.NET Framework也搭配泛型定義了三個常見的delegate,讓開發者減少了delegate宣告的麻煩。

  • Predicate
  • Action
  • Func<T, TResult>

善加使用delegate可以讓系統架構更有彈性,也更能將邏輯封裝在所屬的物件中。

Effective C# 建議

callback應該以.NET的delegate實作

Item 08:對事件叫用使用空條件運算子(Use the Null Conditional Operator for Event Invocations)

.NET的事件處理已經將語法封裝得很簡單,但仍有一些陷阱需要避免。最常見的就是沒有處理程序依附在事件上,會引發NullReferenceException。所以,在處理上會增加null的判斷,還會加上一些處理以避免多執行序所引發的問題。

可以參考.NET 學習紀錄 | Item 8 : Use the Null Conditional Operator for Event Invocations的詳細說明。

為此,C# 6.0推出Null Conditional Operator這個新語法 ?.,可以執行序安全的檢查事件的處理程序是否為null

public void RaiseUpdates()
{
	counter++;
	Updated?.Invoke(this, counter);
}

使用?.運算子時必須使用Invoke(),因為語言並不允許?.運算子後面接著括號。編譯器會對每個delegate或是事件定義產生一個型別安全的Invoke()方法。

Effective C# 建議

新方法簡單且清楚,每次都採用它

參考

Item 09:減少 Boxing 與 Unboxing(Minimize Box and Unboxing)

Boxing 與 Unboxing是.NET處理**值型別(Value Type)物件參與參考型別(Reference Type)**操作時,所自動進行的轉換機制。這樣的機制讓開發者很方便的不必在意這兩種型別型別,就可以自由地進行操作。

不過,這樣的轉換卻對效能有些影響。所以,應該盡可能減少這樣的轉換。

概念上是如果遇到參數型別是System.Object的方法(method),就試著找是否有相對應的泛型方法可以使用。例如.NET 1.x的collection可以放值型別物件,但它是以System.Object的方式處理。所以更好的方式是使用.NET 2.0的泛型collection物件。

另一個例子是以下簡單的字串處理:

Console.WriteLine($"A few numbers:{firstNumber}, {secondNumber}");

建構Interpolated Strings的工作使用System.Object的陣列,所以可以透過ToString()以增進效能

Console.WriteLine($"A few numbers:{firstNumber.ToString()}, {secondNumber.ToString()}");

Effective C# 建議

注意將值型別轉換成System.Object或Interface 型別的程式,以減少Boxing 與 Unboxing

參考

Item 10:只對基底類別更新使用new修飾詞(Use the new Modifier Only to React to Base Class Updates)

C#是物件導向程式,也就意味著會透過繼承以使用其他人所寫的程式碼。有時候是自己團隊成員寫的,有時候也可能會外部廠商所寫的。久了之後,就可能會出現method命名重複的問題。

也就是說,如果我們所寫的子類別提供了一個doSomething(),而原本基底類別(父類別)並沒有,但更新後卻發現基底類別也有doSomething()。 如果是自己團隊成員所寫的,還可以透過溝通以避開這個問題。但如果是外部廠商所寫的,甚至是Framework所提供的,就無法透過修改他們的程式碼以避開這個問題。

所以,C#提供了這個關鍵字 new 來控制所想要的行為。override關鍵字是子類別用來修改基底類別中virtual或abstract的 method,以參與多型的機制。new關鍵字是表示子類別的那個method不參與多型機制。如果不加上任何key word,C#預設會是new,但編譯時會出現警告。

也可以參考天空的垃圾桶 | new和override的差異與目的。這篇文章寫得很清楚,解釋了virtualoverridenew這幾個關鍵字在繼承行為的影響。

Effective C# 建議

new 修飾詞必須小心的使用。如果你不假思索的套用它,你會在你的物件中產生模糊的方法叫用。

參考