簡體   English   中英

c# 中的 GetEnumerator() 是否返回副本或迭代原始來源?

[英]Does the GetEnumerator() in c# return a copy or the iterates the original source?

我有一個簡單的GetEnumerator用法。

private ConcurrentQueue<string> queue = new ConcurrentQueue<string>();

public IEnumerator GetEnumerator()
{
    return queue.GetEnumerator();
}

我想更新這個 class 之外的隊列。

所以,我在做:

var list = _queue.GetEnumerator();
while (list.MoveNext())
{
    list.Current as string = "aaa";
}

GetEnumerator()返回隊列的副本,還是迭代原始值? 所以在更新的時候,我更新原來的?

謝謝:)

這取決於確切的底層實現。

據我所知,大多數內置的 do.net 容器都使用當前數據,而不是快照。

如果您在迭代集合時修改集合,您可能會遇到異常——這是為了防止出現此問題。

不是ConcurrentQueue<T>的情況,因為GetEnumerator方法返回隊列內容的快照(自 .Net 4.6 - Docs起)

IEnumerator 接口在Current屬性上沒有set ,因此您不能以這種方式修改集合( 文檔

在迭代時修改集合(添加、刪除、替換元素)通常是有風險的,因為人們不應該知道迭代器是如何實現的。

除此之外,還創建了一個隊列以獲取第一個元素/在末尾添加元素,但無論如何都不允許替換“中間”的元素。

以下是兩種可行的方法:

方法 1 - 創建一個包含更新元素的新隊列

迭代原始隊列並在此過程中重新創建一個新集合。

var newQueueUpdated = new ConcurrentQueue<string>();
var iterator = _queue.GetEnumerator();
while (iterator.MoveNext())
{
    newQueueUpdated.Add("aaa");
}
_queue = newQueueUpdated;

這自然是通過使用 linq .Select在一個 go 中完成的,並將結果 IEnumerable 提供給 Queue 的構造函數:

_queue  = new ConcurrentQueue<string>(_queue.Select(x => "aaa"));

當心,可能會消耗資源。 當然,其他實現也是可能的,尤其是當您的收藏很大時。

方法 2 - 可變元素的集合

您可以使用包裝器 class 來啟用存儲對象的突變:

public class MyObject
{
    public string Value { get; set; }
}

然后你創建一個private ConcurrentQueue<MyObject> queue = new ConcurrentQueue<MyObject>(); 反而。

現在您可以改變元素,而無需更改集合本身中的任何引用:

var enumerator = _queue.GetEnumerator();
while (enumerator.MoveNext())
{
    enumerator.Current.Value = "aaa";
}

在上面的代碼中,容器存儲的引用從未改變。 不過,他們的內部 state已經改變了。

在問題代碼中,您實際上試圖將一個 object(字符串)更改為另一個 object,這在隊列的情況下並不清楚,並且不能通過只讀的.Current來完成。 對於某些容器,它甚至應該被禁止。

下面是一些測試代碼,看看我是否可以在迭代時修改ConcurrentQueue<string>

ConcurrentQueue<string> queue = new ConcurrentQueue<string>(new[] { "a", "b", "c" });

var e = queue.GetEnumerator();

while (e.MoveNext())
{
    Console.Write(e.Current);
    if (e.Current == "b")
    {
        queue.Enqueue("x");
    }
}

e = queue.GetEnumerator(); //e.Reset(); is not supported
while (e.MoveNext())
{
    Console.Write(e.Current);
}

成功運行並生成abcabcx

但是,如果我們將集合更改為標准的List<string>那么它就會失敗。

這是實現:

List<string> list = new List<string>(new[] { "a", "b", "c" });

var e = list.GetEnumerator();

while (e.MoveNext())
{
    Console.Write(e.Current);
    if (e.Current == "b")
    {
        list.Add("x");
    }
}

e = list.GetEnumerator();
while (e.MoveNext())
{
    Console.Write(e.Current);
}

在拋出InvalidOperationException之前會產生ab

對於ConcurrentQueue ,這是由文檔專門解決的

枚舉表示隊列內容的即時快照。 它不反映調用 GetEnumerator 后對集合的任何更新。 枚舉器可以安全地與隊列的讀取和寫入並發使用。

所以答案是:它就像返回一個副本一樣。 (它實際上並沒有制作副本,但效果就好像它是一個副本 - 即在枚舉它的同時更改原始集合不會更改枚舉產生的項目。)

對於其他類型不保證此行為 - 例如,如果列表在枚舉期間被修改,則嘗試枚舉List<T>將失敗。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

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