簡體   English   中英

IEnumerable 在 Array 和 List 上的表現不同

[英]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()實現仍然可以返回一個克隆,這會帶來非常相似的問題。 碰巧我的問題是不同的。

更新2

這是我認為我的問題的一個例子。 謝謝大家的信息。

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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM