編輯02 - 2010:
在我看來,至少有兩種方法來解釋這個問題。
爲什麼該Select<T, TResult>
擴展方法中,當 上的類的一個實例稱爲該 實現ICollection<T>
,不 返回其提供 Count
屬性的對象;以及爲什麼 Count<T>
擴展方法不是 檢查此屬性,以便當 方法鏈接時提供O(1)性能?
這個版本的問題,使有關LINQ的擴展是如何工作的任何虛假假設,是因爲到ICollection<T>.Select.Count
一個呼叫,畢竟,總是返回相同的值ICollection<T>.Count
一個有效的問題。這就是梅爾達德對這個問題的解釋,他已經提供了一個徹底的迴應。
但我將問題閱讀爲詢問...
如果Count<T>
擴展方法一類的一個目的 實施ICollection<T>
提供O(1) 性能,爲什麼 它提供爲O(n)的性能爲 的 Select<T, TResult>
延伸的返回值方法?
在這個版本的問題,有一個錯誤的假設:即LINQ的擴展方法通過組裝的小集合後,一個又一個(在內存中),並通過IEnumerable<T>
接口暴露他們一起工作。
如果是這樣的LINQ的擴展是如何工作的,該Select
方法可能是這個樣子:
public static IEnumerable<TResult> Select<T, TResult>(this IEnumerable<T> source, Func<T, TResult> selector) {
List<TResult> results = new List<TResult>();
foreach (T input in source)
results.Add(selector(input));
return results;
}
而且,如果這是的Select
實施,我想你會發現大部分的代碼,利用這種方法會表現得完全一樣。但這樣做會很浪費,而且事實上會在我的原始答案中描述的某些情況下導致例外。
在現實中,我相信Select
方法的實現是非常接近這樣的事情:
public static IEnumerable<TResult> Select<T, TResult>(this IEnumerable<T> source, Func<T, TResult> selector) {
foreach (T input in source)
yield return selector(input);
yield break;
}
這是爲了提供懶惰的評價,並解釋了爲什麼Count
屬性不爲O訪問(1 )時間到Count
方法。
換句話說
所以,而邁赫達德回答爲什麼Select
沒有設計不同,這樣Select.Count
會表現不同的的問題,我提出我的最佳答案的的問題,爲什麼Select.Count
的行爲方式,是否。
原來的答案:
方法的副作用是沒有答案的。
根據邁赫達德的回答是:
這其實並不重要的是選擇的 結果懶洋洋地評估。
我不買這個。讓我解釋一下爲什麼。
對於初學者來說,可以考慮以下兩個非常相似的方法:
public static IEnumerable<double> GetRandomsAsEnumerable(int N) {
Random r = new Random();
for (int i = 0; i < N; ++i)
yield return r.NextDouble();
yield break;
}
public static double[] GetRandomsAsArray(int N) {
Random r = new Random();
double[] values = new double[N];
for (int i = 0; i < N; ++i)
values[i] = r.NextDouble();
return values;
}
OK,就這些方法做什麼?每個用戶都會返回任意數量的隨機雙打(最多可達int.MaxValue
)。這兩種方法是否被懶惰地評估過或者沒有關係?要回答這個問題,我們來看看下面的代碼:
public static double Invert(double value) {
return 1.0/value;
}
public static void Test() {
int a = GetRandomsAsEnumerable(int.MaxValue).Select(Invert).Count();
int b = GetRandomsAsArray(int.MaxValue).Select(Invert).Count();
}
你猜這兩個方法調用會發生什麼嗎?讓我饒了你將該代碼複製,並測試它自己的麻煩:
的第一變量,a
,將(之後的時間可能顯著量)被初始化爲int.MaxValue
(目前2147483647)。 second one,b
,很可能會被OutOfMemoryException
打斷。
由於Select
和其他Linq擴展方法是懶惰評估,它們允許你做你根本無法做的事情。以上是一個相當平凡的例子。但我的主要觀點是質疑懶惰評估並不重要。 Mehrdad的聲明說,一個Count
財產「從一開始就真正知道並且可以優化」實際上引發了這個問題。 Select
方法可能看起來很簡單,但Select
並不是特別的;它返回一個IEnumerable<T>
就像Linq擴展方法的其餘部分一樣,對於這些方法來「知道」Count
的返回值需要完整的集合才能被緩存,因此禁止懶惰評估。
懶惰評價就是答案。
由於這個原因,我不得不同意一位原來的迴應者(他們的回答現在似乎已經消失),懶惰的評估真的是這裏的答案。方法副作用需要考慮的觀點實際上是次要的,因爲無論如何,這已經被確保爲副作用。
後記:我做出了非常自信的陳述,並強調了我的觀點,主要是因爲我想澄清我的論點是什麼,而不是對任何其他反應(包括Mehrdad的)的任何不敬,錯過了商標。
ICollection的索引器屬性有副作用嗎?現在在Count方法中實現的優化是否避免調用集合的索引器屬性,這不是一個問題嗎? – shojtsy 2010-02-01 23:56:42
@shojtsy:索引器是無關緊要的,LINQ方法從來沒有使用過,但是你的關注是有效的,因爲'Count'屬性和'GetEnumerator'方法也可能有副作用。這就是爲什麼'Count()'方法的文檔明確地明確指出'ICollection'並且說如果參數實現了該接口而不是枚舉,則它使用'Count'屬性。 如果這在文檔中不明確,我會期望'Count()'**不要**使用'ICollection .Count'。 –
2010-02-02 00:00:11
您的答案可能直接針對這個*精確*場景('ICollection'上的'選擇'),但我認爲這是誤導性的,因爲它很容易被錯誤地解釋爲暗示這種潛在的優化曾經受到嚴肅考慮,並且只能被解僱以便允許副作用。在我的回答中,我試圖:首先:解釋爲什麼Linq擴展*一般*使用懶惰評估;第二:指出'Select'不是*特殊的,並且和其他任何Linq擴展一樣。 – 2010-02-02 15:19:30