2009-07-31 116 views
298

我的問題如上所述。例如,如何將項添加到IEnumerable <T>集合中?

IEnumerable<T> items = new T[]{new T("msg")}; 
items.ToList().Add(new T("msg2")); 

但畢竟它只有1項內。

我們可以有一個像items.Add(item)這樣的方法嗎?

List<T>

+0

'IEnumerable '僅用於查詢集合。它是LINQ框架的支柱。它總是一些其他集合的抽象,例如'Collection ','List '或'Array'。該接口只提供了一個'GetEnumerator'方法,該方法返回一個'IEnumerator '的實例,該實例可以一次擴展一個項目。 LINQ擴展方法受此界面限制。 'IEnumerable '被設計爲只讀,因爲它可能代表多個集合的部分集合。 – Jordan 2016-01-27 16:04:56

+1

對於這個例子,只聲明'IList '而不是'IEnumerable ':'IList items = new T [] {new T(「msg」)}; items.Add(new T(「msg2」));' – NightOwl888 2017-05-07 15:56:57

回答

357

您不能,因爲IEnumerable<T>不一定代表可以添加項目的集合。事實上,它並不一定代表一個集合!例如:

IEnumerable<string> ReadLines() 
{ 
    string s; 
    do 
    { 
      s = Console.ReadLine(); 
      yield return s; 
    } while (s != ""); 
} 

IEnumerable<string> lines = ReadLines(); 
lines.Add("foo") // so what is this supposed to do?? 

你能做什麼,但是,是創建(未指定類型)的IEnumerable對象,其中,列舉時,將提供舊的所有項目,加上一些自己的。您可以使用Enumerable.Concat爲:

items = items.Concat(new[] { "foo" }); 

不會改變數組對象(你不能將項目插入到陣列,反正)。但它會創建一個新的對象,列出數組中的所有項目,然後是「Foo」。此外,新對象會跟蹤數組中的更改(即,每當您枚舉它時,都會看到項目的當前值)。

+3

創建一個數組來添加單個值的開銷有點高。定義單個值Concat的擴展方法可以重複使用一點。 – JaredPar 2009-07-31 02:08:45

+3

這取決於 - 它可能是值得的,但我很想把它稱爲過早優化,除非重複地在循環中調用'Concat';如果是的話,無論如何你都會遇到更大的問題,因爲每次你使用'Concat'時,你會得到一個枚舉器層次 - 所以到最後,單次調用MoveNext會導致一系列長度相等的調用到迭代次數。 – 2009-07-31 02:13:20

+1

@Pavel,heh。通常情況下,創建陣列的內存開銷並不會讓我感到困擾。這是額外的打字,我覺得煩人:)。 – JaredPar 2009-07-31 02:15:34

12

沒有了IEnumerable好好嘗試一下支持將項目添加到它。

你有一個'替代'。

var List = new List(items); 
List.Add(otherItem); 
+3

您的建議不是唯一的選擇。例如,ICollection和IList在這裏都是合理的選擇。 – jason 2009-07-31 02:07:53

+5

但是你不能實例化。 – 2009-07-31 02:42:14

+4

當然,你也不能實例化 - 它們是接口。 – 2010-10-15 15:16:23

74

IEnumerable<T>類型不支持這種操作。 IEnumerable<T>接口的目的是讓消費者查看集合的內容。不要修改這些值。

當您執行像.ToList()。Add()這樣的操作時,您將創建一個新的List<T>並向該列表中添加一個值。它與原始列表沒有關係。

你可以做的是使用Add擴展方法創建一個新的IEnumerable<T>與增值。

items = items.Add("msg2"); 

即使在這種情況下,它不會修改原始IEnumerable<T>對象。這可以通過參考它來驗證。例如

var items = new string[]{"foo"}; 
var temp = items; 
items = items.Add("bar"); 

這組操作後,將變量temp將仍然只能引用可枚舉與該組值中的單個元素「foo」的,而項目將參考不同的枚舉與值「foo」的和「bar 」。

編輯

我contstantly忘了加不上IEnumerable<T>一個典型的擴展方法,因爲它是我最終確定了第一批之一。這是

public static IEnumerable<T> Add<T>(this IEnumerable<T> e, T value) { 
    foreach (var cur in e) { 
    yield return cur; 
    } 
    yield return value; 
} 
+9

這就是所謂的'Concat' :) – 2009-07-31 02:01:23

+1

@Pavel,謝謝你指出。即使Concat也不行,因爲它需要另一個IEnumerable 。我粘貼了我在項目中定義的典型擴展方法。 – JaredPar 2009-07-31 02:06:28

+7

雖然我不會稱之爲「添加」,因爲實際上任何其他.NET類型的「添加」(不僅僅是集合)都會就地改變集合。也許'用'?或者它甚至可能只是'Concat'的另一個重載。 – 2009-07-31 02:15:07

8

不僅喜歡你的國家,你能不能添加項目,但如果添加一個項目到List<T>(或幾乎任何其他非只讀集合),您必須對現有的枚舉,該枚舉器失效(從此之後拋出InvalidOperationException)。

如果您彙集來自某種類型的數據查詢的結果,你可以使用Concat擴展方法:

編輯:我原來使用的Union擴展的例子,這是不是真的正確。我的應用程序廣泛使用它來確保重疊查詢不會重複結果。

IEnumerable<T> itemsA = ...; 
IEnumerable<T> itemsB = ...; 
IEnumerable<T> itemsC = ...; 
return itemsA.Concat(itemsB).Concat(itemsC); 
4

其他人已經給出了很好的解釋,爲什麼你不能(也不應該)能夠將項目添加到IEnumerable。我只會補充一點,如果您希望繼續編碼到代表集合的接口並希望添加方法,則您應該編碼爲ICollectionIList。作爲一個附加的富礦,這些接口實現IEnumerable

41

你有沒有使用ICollection<T>IList<T>接口,而不是考慮他們存在,你想有一個「添加」方法對一個IEnumerable <牛逼>的真正原因。 IEnumerable <T>用於將某個類型標記爲......良好......可枚舉或只是一系列項目,而不必確保真實底層對象是否支持添加/刪除項目。還請記住,這些接口實現IEnumerable <T>,因此您可以使用IEnumerable <T>獲得所有擴展方法。

25

幾篇短,甜美的擴展上IEnumerableIEnumerable<T>方法做到這一點對我來說:

public static IEnumerable Append(this IEnumerable first, params object[] second) 
{ 
    return first.OfType<object>().Concat(second); 
} 
public static IEnumerable<T> Append<T>(this IEnumerable<T> first, params T[] second) 
{ 
    return first.Concat(second); 
} 
public static IEnumerable Prepend(this IEnumerable first, params object[] second) 
{ 
    return second.Concat(first.OfType<object>()); 
} 
public static IEnumerable<T> Prepend<T>(this IEnumerable<T> first, params T[] second) 
{ 
    return second.Concat(first); 
} 

優雅的(當然,除了非通用版本)。太糟糕了,這些方法不在BCL中。

+0

第一個Prepend方法給我一個錯誤。 「'object []'不包含'Concat'的定義和最佳擴展方法重載'System.Linq.Enumerable.Concat (System.Collections.Generic.IEnumerable ,System.Collections.Generic.IEnumerable )'有一些無效的論點「 – 2013-07-23 14:11:12

+0

@TimNewton你做錯了。誰知道爲什麼,因爲沒有人能從這段錯誤代碼中看出來。 Protip:總是捕獲異常並在其上執行'ToString()',因爲捕獲了更多的錯誤細節。我猜你沒有在文件頂部包含System.Linq;但是誰知道?嘗試自行研究,創建一個最小原型,如果你不能,那麼會出現同樣的錯誤,然後在問題中尋求幫助。 – Will 2013-07-23 14:45:32

+0

我剛剛複製並粘貼上面的代碼。除了提到我提到的錯誤的Prepend之外,它們都可以正常工作。它不是一個運行時異常,它是一個編譯時異常。 – 2013-07-23 16:32:01

1

你可以這樣做。

//Create IEnumerable  
IEnumerable<T> items = new T[]{new T("msg")}; 

//Convert to list. 
List<T> list = items.ToList(); 

//Add new item to list. 
list.add(new T("msg2")); 

//Cast list to IEnumerable 
items = (IEnumerable<T>)items; 
+7

這個問題在5年前以更完整的方式回答。將接受的答案看作問題的良好答案的例子。 – pquest 2014-11-28 18:38:40

0

Easyest方法就是

IEnumerable<T> items = new T[]{new T("msg")}; 
List<string> itemsList = new List<string>(); 
itemsList.AddRange(items.Select(y => y.ToString())); 
itemsList.Add("msg2"); 

然後你就可以回到列表爲IEnumerable還因爲它實現IEnumerable接口

9

要添加第二個消息,則需要 -

IEnumerable<T> items = new T[]{new T("msg")}; 
items = items.Concat(new[] {new T("msg2")}) 
+0

但您還需要將Concat方法的結果分配給項目對象。 – 2017-02-09 15:33:30

+0

是的,托馬斯你是對的。我修改了最後一個表達式來將連接值分配給項目。 – Aamol 2017-02-17 03:06:36

-4

當然,你可以(我將離開你的T-business):

public IEnumerable<string> tryAdd(IEnumerable<string> items) 
{ 
    List<string> list = items.ToList(); 
    string obj = ""; 
    list.Add(obj); 

    return list.Select(i => i); 
} 
1

我只是來這裏說,除了Enumerable.Concat擴展方法,似乎是在.NET核心1.1.1命名Enumerable.Append另一種方法。後者允許您將單個項目連接到現有序列。所以Aamol的回答也可以寫成

IEnumerable<T> items = new T[]{new T("msg")}; 
items = items.Append(new T("msg2")); 

不過,請注意,此功能不會改變輸入序列,它只是返回一個包裝,把給定的順序和附加項目一起。

3

在.net核心中,有一個方法Enumerable.Append正是如此。

該方法的源代碼是available on GitHub. ....實現(比其他答案中的建議更復雜)值得一看:)。

相關問題