在日常編程中,經常遇到要在一組復雜類的集合(Collection)中做比較、取最大值或最小值。
舉個最簡單的例子,我們要在一個如下結構的集合中選取包含最大值的元素:
public class Class<T> where T : struct { public T? Value { get; set; } }
var ints = new List<Class<int>>() { new Class<int>() { Value = 2 }, new Class<int>() { Value = 10 }, new Class<int>() { Value = 5 }, new Class<int>() { Value = 10 }, };
如果不使用.Net高級特性的做法通常是:
var max = new Class<int>() { Value = Int32.MinValue }; foreach (var i in ints) { if (i.Value != null && i.Value > max.Value) { max = i; } } return max;
這樣的寫法,除了煩瑣無味以外,還有一個很明顯的Bug,雖然在上面這個例子中暴露不出來,但是假設集合沒有一個元素,或者組成如下:
var ints = new List<Class<int>>() { new Class<int>() { Value = null }, new Class<int>() { Value = null }, new Class<int>() { Value = null }, };
此時此刻,我們想要返回的是包含null值的元素,而上述方法則帶給我們包含Int32最小值的元素,此元素並不在ints集合中!
你會想使用Linq框架的Max(),比如:
var max = ints.Max(i => i.Value);
但事實上這個方法只能返回元素的Value成員變量,也就是int?類型,所以這也不是我們想要的。
此時此刻,正確的方法可能是:
var max = ints.First(i => i.Value == ints.Max(j => j.Value));
但,這還是有機會拋異常的,假設集合如下
var ints = new List<Class<int>>() { new Class<int>() { Value = null }, new Class<int>() { Value = null }, new Class<int>() { Value = null }, null };
在這個集合裡我們加了一個真正的null元素(而非包含null值的元素),ints.Max()就會拋NullReferenceException。
為了解決這個問題,我們可以將max聲明改寫為:
var max = ints.First(i => i.Value == ints.Max(j => { return j == null ? null : j.Value; }));
你可能已經注意到了,Max()方法中加了null值判斷,但First()方法中卻不需要。請先不要急於抱怨微軟設計的集合方法有行為不一致的現象。
至於為什麼,我將這個作業交給你,先自己在下面這個鏈接裡挖掘一下答案吧。:)
http://referencesource.microsoft.com/
(此網站是微軟早在兩年前建設好的用於方便軟件工程師查看.Net源代碼的網站)