本文要點
- 應遵循《.NET設計規范:.NET約定慣用法與模式》一書。和十年前第一版出版時一樣,書中給出的原則在當前依然有指導意義。
- API設計是最重要的。設計不好的API會在極大地增加軟件缺陷,同時降低可重用性。
- 時刻牢記“良性循環”(Pit of Success)這一哲理:讓正確的事情更易於做,讓犯錯誤更加困難。
- 移除“線路噪音”(Line Noise)和“樣板”(Boilerplate)代碼,聚焦於對業務邏輯的關注。
- 出於性能考慮而犧牲代碼清晰度前,請認真考慮一下。
C# 7是一個重大更新,其中提供了很多有意思的新功能。雖然已有大量的文章介紹這些功能可以做什么,但是鮮有文章介紹應如何使用這些功能。本文將過一遍《.NET設計規范:.NET約定慣用法與模式》 (譯者注:英文書名為“Framework Design Guidelines: Conventions, Idioms, and Patterns for Reusable .NET Libraries”)一書中給出的指導原則,力圖更好地使用C# 7的新特性。
元組返回(Tuple Returns)
通常在C#編程中,一個函數返回多個值實現起來十分繁瑣。一種做法是使用輸出參數,這只適用於暴露異步方法的情況。另一種做法是使用 Tuple<T>。創建Tuple<T>過於啰嗦,需要做內存分配,並且Tuple的字段沒有描述性名字。也可以使用自定義的結 構體。雖然結構體在性能上要優於元組,但是大量使用一次性類型會將代碼弄得一團糟。而使用具有動態特性的匿名類型,存在性能不好的問題,還缺少靜態類型檢 查。
在C# 7中新提供了元組返回語法,它解決了全部上述問題。下面給出一個基本語法的例子:
public (string, string) LookupName(long id) // tuple return type { return ("John", "Doe"); //元組常值。 } var names = LookupName(0); var firstName = names.Item1; var lastName = names.Item2;
該函數的實際返回類型是ValueTuple<string, string>。正如名稱所示,ValueTuple<string, string>類似於Tuple<T>類,是一個輕量級的結構體。它解決了類型膨脹(Type Bloat)問題,但是依然沒有解決描述性名稱這一困擾Tuple<T>的問題。我們看一下如下的例子:
public (string First, string Last) LookupName(long id) var names = LookupName(0); var firstName = names.First; var lastName = names.Last;
其中的返回類型依然是ValueTuple<string, string>,但是現在編譯器在函數中添加了一個TupleElementNames屬性。這樣調用該函數的代碼就可以使用描述性名稱,而不再是Item1或Item2這樣的名稱了。
警告: TupleElementNames屬性只能由編譯器賦予。如果返回類型上使用了反射,你將只能看到裸的ValueTuple<T>結構體。因為在獲得結果時,屬性是位於函數本身上,而這個信息丟失了。
編譯器會盡可能維護額外類型的幻象。例如,給出如下這些聲明:
var a = LookupName(0); (string First, string Last) b = LookupName(0); ValueTuple<string, string> c = LookupName(0); (string make, string model) d = LookupName(0);
在編譯器看來,a和b同是(string First, string Last)。鑒於c被顯式聲明為ValueTuple<string, string>,因此不存在c.First屬性。
該例中d的賦值語句展示了這一設計的失靈之處,即會在一定程度上導致缺失類型安全。字段意外地重命名是一個非常容易發生的問題,一個元組可以錯誤地 指定給另一個恰好具有同樣形狀的元組。這同樣是由於編譯器沒有真正地將(string First, string Last)和(string make, string model)區分為不同的類型。
ValueTuple是可變的
有意思的是, ValueTuple是可變的。Mads Torgersen給出了這樣的解釋:
為什么通常可變結構體是不好的,不要應用於元組?下面給出原因。
如果你按正常的封裝方式編寫了一個可變結構體,並且其中具有私有的狀態,還有公開的修改器(Mutator)屬性和方法,那么你可能就會陷入一些嚴重的錯誤中。因為只要結構體是保持在只讀變量中,那么修改器就會默默地工作於結構體的一個拷貝上!
但是元組的確有公開的可變字段。它在設計上並未考慮修改器,因此不存在出現上述現象的風險。
此外,ValueTuple是結構體,而結構體在傳遞時需要進行拷貝。結構體並不直接在線程間共享,也不承擔“共享可變狀態”的風險。這不同於System.Tuple家族的類型,這些類型也是類。為確保線程安全,需要這些類型是不可變的。
注意,這里Torgersen所指的是“字段”,而不是“屬性”。對於使用元組返回函數結果的反射庫,這會導致問題。
元組返回的指導原則
- 當字段列表規模較小並不會發生更改時,考慮使用元組返回,而不是out參數。
- 對元組返回中的描述性名字使用帕斯卡拼寫法(PascalCase),這會使得元組字段看上去就像是正常的類和結構中的屬性。
- 在不進行解析就讀取元組返回時,使用var,以避免意外地誤標字段。
- X 如果想要對返回值使用反射,應避免返回值元組。
- X 如果在未來的版本中可能會返回額外的字段,那么就不要在公開API上使用元組返回。在元組返回中添加字段是一種破壞性變更。
----------------------------------------------------節選自infoQ:C# 7編程模式與實踐-----------------------------------------------------------------