[英]IEnumerable performs differently on Array vs List
這個問題更像是“我的理解是否准確”,如果不正確,請幫助我理解它。 我有這段代碼來解釋我的問題:
class Example
{
public string MyString { get; set; }
}
var wtf = new[] { "string1", "string2"};
IEnumerable<Example> transformed = wtf.Select(s => new Example { MyString = s });
IEnumerable<Example> transformedList = wtf.Select(s => new Example { MyString = s }).ToList();
foreach (var i in transformed)
i.MyString = "somethingDifferent";
foreach (var i in transformedList)
i.MyString = "somethingDifferent";
foreach(var i in transformed)
Console.WriteLine(i.MyString);
foreach (var i in transformedList)
Console.WriteLine(i.MyString);
它輸出:
string1
string2
somethingDifferent
somethingDifferent
乍一看,兩個Select()方法都返回IEnumerable< Example> 。 但是,基礎類型是WhereSelectArrayIterator< string, Example>和List< Example > 。
這就是我的理智開始受到質疑的地方。 根據我的理解,上面輸出的差異是因為兩種底層類型實現GetEnumerator()方法的方式。
使用這個方便的網站,我能夠(我認為)追蹤導致差異的代碼位。
class WhereSelectArrayIterator<TSource, TResult> : Iterator<TResult>
{ }
查看第 169 行的內容,將我指向Iterator< TResult> ,因為這就是調用GetEnumerator()的地方。
從第 90 行開始,我看到:
public IEnumerator<TSource> GetEnumerator() {
if (threadId == Thread.CurrentThread.ManagedThreadId && state == 0) {
state = 1;
return this;
}
Iterator<TSource> duplicate = Clone();
duplicate.state = 1;
return duplicate;
}
我從中收集到的是,當您枚舉它時,您實際上是在枚舉克隆的源(如 WhereSelectArrayIterator 類的 Clone() 方法中所寫)。
這將滿足我需要了解現在,但作為獎勵,如果有人可以幫助我弄清楚這是為什么不回我第一次枚舉數據。 據我所知,第一遍狀態應該= 0。 除非,從不同線程調用相同方法的引擎蓋下可能發生了魔法。
在這一點上,我認為我的“發現”有點誤導(該死的克隆方法把我帶入了錯誤的兔子洞),這確實是由於延遲執行。 我錯誤地認為即使我推遲了執行,一旦第一次枚舉它就會將這些值存儲在我的變量中。 我應該更清楚; 畢竟我在Select 中使用了new關鍵字。 也就是說,它仍然讓我看到了一個想法,即特定類的GetEnumerator()實現仍然可以返回一個克隆,這會帶來非常相似的問題。 碰巧我的問題是不同的。
這是我認為我的問題的一個例子。 謝謝大家的信息。
IEnumerable<Example> friendly = new FriendlyExamples();
IEnumerable<Example> notFriendly = new MeanExamples();
foreach (var example in friendly)
example.MyString = "somethingDifferent";
foreach (var example in notFriendly)
example.MyString = "somethingDifferent";
foreach (var example in friendly)
Console.WriteLine(example.MyString);
foreach (var example in notFriendly)
Console.WriteLine(example.MyString);
// somethingDifferent
// somethingDifferent
// string1
// string2
支持類:
class Example
{
public string MyString { get; set; }
public Example(Example example)
{
MyString = example.MyString;
}
public Example(string s)
{
MyString = s;
}
}
class FriendlyExamples : IEnumerable<Example>
{
Example[] wtf = new[] { new Example("string1"), new Example("string2") };
public IEnumerator<Example> GetEnumerator()
{
return wtf.Cast<Example>().GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return wtf.GetEnumerator();
}
}
class MeanExamples : IEnumerable<Example>
{
Example[] wtf = new[] { new Example("string1"), new Example("string2") };
public IEnumerator<Example> GetEnumerator()
{
return wtf.Select(e => new Example(e)).Cast<Example>().GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return wtf.Select(e => new Example(e)).GetEnumerator();
}
}
Linq 的工作原理是讓每個函數返回另一個 IEnumerable,它通常是一個延遲處理器。 在最終返回的 Ienumerable 的枚舉發生之前,不會發生實際的執行。 這允許創建高效的管道。
當你做
var transformed = wtf.Select(s => new Example { MyString = s });
選擇代碼尚未實際執行。 只有當您最終枚舉轉換時,才會完成選擇。 即在這里
foreach (var i in transformed)
i.MyString = "somethingDifferent";
請注意,如果您這樣做
foreach (var i in transformed)
i.MyString = "somethingDifferent";
管道將再次執行。 這沒什么大不了的,但如果涉及 IO,它可能會很大。
這條線
var transformedList = wtf.Select(s => new Example { MyString = s }).ToList();
是相同的
var transformedList = transformed.ToList();
真正的大開眼界是將調試語句或斷點放在 where 或 select 中以實際查看延遲的管道執行
閱讀 linq 的實現很有用。 這里是選擇https://referencesource.microsoft.com/#System.Core/System/Linq/Enumerable.cs,5c652c53e80df013,references
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.