IEnumerable <T> koleksiyonuna nasıl öğe ekleyebilirim?


405

Yukarıdaki başlık olarak sorum. Örneğin,

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

ama sonuçta içinde sadece 1 öğe var.

Gibi bir yöntem alabilir miyiz items.Add(item)?

gibi List<T>


4
IEnumerable<T>yalnızca koleksiyonları sorgulamak içindir. LINQ çerçevesinin belkemiğidir. Her zaman gibi diğer bazı koleksiyonunun bir soyutlamadır Collection<T>, List<T>ya, Array. Arabirim yalnızca , genişleyen koleksiyonun her seferinde bir öğe yürümesini sağlayan GetEnumeratorbir örneğini döndüren bir yöntem IEnumerator<T>sağlar. LINQ genişletme yöntemleri bu arayüzle sınırlıdır. IEnumerable<T>yalnızca birden çok koleksiyonun parçalarının bir araya gelmesini temsil edebileceği için okunmak üzere tasarlanmıştır.
Ürdün

3
Bu örnek için, sadece beyan IList<T>yerine IEnumerable<T>:IList<T> items = new T[]{new T("msg")}; items.Add(new T("msg2"));
NightOwl888

Yanıtlar:


480

Yapamazsınız, çünkü IEnumerable<T>öğelerin eklenebileceği bir koleksiyonu temsil etmez. Aslında, mutlaka bir koleksiyonu temsil etmez! Örneğin:

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

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

Bununla birlikte, yapabileceğiniz şey, numaralandırıldığında, eskisinin tüm öğelerini artı kendi öğelerinizi sağlayacak yeni bir IEnumerable nesne (belirtilmemiş türde) oluşturmaktır . Bunun için kullanın Enumerable.Concat:

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

Bu , dizi nesnesini değiştirmez (yine de dizilere öğe ekleyemezsiniz). Ancak, dizideki tüm öğeleri listeleyen yeni bir nesne oluşturacak ve sonra "Foo" oluşturacaktır. Ayrıca, bu yeni nesne dizideki değişiklikleri izler (yani numaralandırdığınızda öğelerin geçerli değerlerini görürsünüz).


3
Tek bir değer eklemek için bir dizi oluşturmak ek yükte biraz yüksektir. Tek bir değer Concat için bir genişletme yöntemi tanımlamak biraz daha kullanılabilir.
JaredPar

4
O bağlıdır - buna değer olabilir, ancak Concattekrar tekrar bir döngüde çağrılmadığı sürece erken optimizasyon olarak adlandırmak için cazipim; ve eğer öyleyse, yine de daha büyük sorunlarınız var, çünkü her seferinde Concatbir tane daha numaralandırıcı katmanı elde edersiniz - bu yüzden sonunda tek çağrı MoveNext, yineleme sayısına eşit bir çağrı zinciriyle sonuçlanır.
Pavel Minaev

2
@Pavel, heh. Genellikle beni rahatsız eden diziyi oluşturmanın bellek yükü değildir. Can sıkıcı bulduğum ekstra yazım :).
JaredPar

2
Bu yüzden IEnumerable'ın ne yaptığını biraz daha anlıyorum. Sadece değiştirilmeye niyetli olmamalıdır. Thank you guys
ldsenow

7
IEnumerable<T>C # için sözdizimsel şeker için bir Connect özellik isteği vardı , aşırı yükleme operator+de dahil olmak üzere Concat(Bu arada, VB'de, IEnumerablebir dizi gibi dizine ekleyebilirsin - ElementAtbaşlık altında kullanır ): connect.microsoft.com / VisualStudio / feedback /… . Ayrıca, Concathem solda hem de sağda tek bir öğe almanın iki aşırı yüklenmesi varsa , bu yazmayı xs +=x kısaltır - bu yüzden C # 5.0'da öncelik vermek için Mads'i (Torgersen) düzeltin :)
Pavel Minaev

98

Tür IEnumerable<T>, bu tür işlemleri desteklemez. IEnumerable<T>Arayüzün amacı, tüketicinin bir koleksiyonun içeriğini görmesine izin vermektir. Değerleri değiştirmemek.

.ToList (). Add () gibi işlemler yaptığınızda yeni List<T>bir liste oluşturursunuz ve bu listeye bir değer eklersiniz . Orijinal listeyle bağlantısı yoktur.

Yapabileceğiniz şey, IEnumerable<T>katma değerle yeni bir tane oluşturmak için Uzantı ekle yöntemini kullanmaktır .

items = items.Add("msg2");

Bu durumda bile orijinal IEnumerable<T>nesneyi değiştirmez . Bu, bir referans tutarak doğrulanabilir. Örneğin

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

Bu işlem kümesinden sonra değişken temp hala değerler kümesinde sadece tek bir eleman "foo" ile numaralandırılabilir bir değere referans verirken, öğeler "foo" ve "bar" değerleri ile farklı bir numaralandırılabilir referans olacak.

DÜZENLE

İçeriğin, tipik bir uzantı yöntemi olmadığını, IEnumerable<T>çünkü tanımladığım ilk yöntemlerden biri olduğunu unutuyorum . İşte burada

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

12
Buna Concat:)
Pavel Minaev 31:01

3
@Pavel, işaret ettiğin için teşekkürler. Concat bile işe yaramaz çünkü başka bir IEnumerable <T> gerektirir. Projelerimde tanımladığım tipik uzatma yöntemini yapıştırdım.
JaredPar

10
Ben hemen hemen başka herhangi bir .NET türü (sadece koleksiyonlar) yerinde yerinde değiştirir Add, çünkü ben bunu çağırmak olmaz Add. Belki With? Ya da başka bir aşırı yük bile olabilir Concat.
Pavel Minaev

4
ConcatAşırı yüklenmenin muhtemelen daha iyi bir seçim olacağını kabul ediyorum çünkü Addgenellikle değişebilirlik anlamına geliyor.
Dan Abramov

15
Bedeni değiştirmeye ne dersiniz return e.Concat(Enumerable.Repeat(value,1));?
Roy Tinker

58

Kullanmayı ICollection<T>veya IList<T>arayüzleri kullanmayı düşündünüz mü , bir Addyöntemde olmasını istediğiniz nedenden dolayı var olurlar IEnumerable<T>.

IEnumerable<T>bir türü, gerçek temeldeki nesnenin öğelerin eklenmesini / kaldırılmasını destekleyip desteklemediğine dair herhangi bir garanti vermeden ... iyi, numaralandırılabilir veya sadece bir dizi öğeyi 'işaretlemek' için kullanılır. Ayrıca, bu arayüzlerin uygulandığını unutmayın, IEnumerable<T>böylece aldığınız tüm uzantı yöntemlerini elde edersiniz IEnumerable<T>.


31

Birkaç kısa, tatlı uzatma yöntemi IEnumerableve IEnumerable<T>benim için yap:

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);
}

Zarif (jenerik olmayan versiyonlar hariç). Bu yöntemler BCL'de çok kötü değil.


İlk Prepend yöntemi bana bir hata veriyor. "'object []', 'Concat' için bir tanım ve en iyi uzantı yöntemi aşırı yüklemesi 'System.Linq.Enumerable.Concat <TSource> (System.Collections.Generic.IEnumerable <TSource>, System.Collections.Generic. IEnumerable <TSource>) 'bazı geçersiz argümanlara sahip "
Tim Newton

@TimNewton yanlış yapıyorsun. Kimse neden bilir, çünkü kimse bu hata kod snippet'inden anlatamaz. Protip: Her zaman istisnayı yakalayın ve ToString()daha fazla hata ayrıntısı yakaladığından bunu yapın. include System.Linq;Dosyanızın en üstünde olmadığını sanmıştım ama kim bilir? Kendi başınıza çözmeyi deneyin, yapamıyorsanız aynı hatayı gösteren minimum bir prototip oluşturun ve ardından bir soruda yardım isteyin.

Yukarıdaki kodu kopyalayıp yapıştırdım. Bahsettiğim hatayı veren Prepend dışında hepsi tamam çalışıyor. Onun bir çalışma zamanı istisna değil derleme zaman istisnası.
Tim Newton

@TimNewton: Ne hakkında konuştuğunuzu bilmiyorum mükemmel çalışıyor Açıkçası yanılıyorsunuz, düzenlemedeki değişiklikleri görmezden geliyorsunuz, yoluma çıkmalıyım, iyi günler, efendim.

belki de çevremizle ilgili farklı bir şey. VS2012 ve .NET 4.5 kullanıyorum. Artık bu konuda zaman kaybetmeyin ben sadece küçük bir de olsa yukarıdaki kod ile ilgili bir sorun olduğunu işaret olacağını düşündüm. Ben bu yanıtı yararlı olarak zaten iptal ettim. Teşekkürler.
Tim Newton


22

Hayır, IEnumerable buna öğe eklemeyi desteklemez.

'Alternatif' sizsiniz:

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

4
Öneriniz tek alternatif değil. Örneğin, ICollection ve IList buradaki makul seçeneklerdir.
jason

6
Ama siz de başlatamazsınız.
Paul van BRENK

16

İkinci mesaj eklemek için -

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

1
Ancak Concat yönteminin sonucunu items nesnesine atamanız gerekir.
Tomasz Przychodzki

1
Evet, Tomasz haklısın. Öğelere birleştirilmiş değerler atamak için son ifadeyi değiştirdim.
Aamol

8

Yalnızca sizin belirttiğiniz gibi öğeler eklemekle kalmaz, aynı List<T>zamanda mevcut bir numaralandırıcınız olan bir öğeye (veya hemen hemen salt okunur olmayan herhangi bir koleksiyona) bir öğe eklerseniz , numaralandırıcı geçersiz kılınır ( InvalidOperationExceptiono andan itibaren atar ).

Bazı veri sorgusu türlerinden sonuçları topluyorsanız, Concatuzantı yöntemini kullanabilirsiniz :

Düzenleme: Aslında Unionaslında doğru değil, örnekte uzantısını kullandım . Uygulamam, çakışan sorguların sonuçları yinelemediğinden emin olmak için yoğun bir şekilde kullanıyor.

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

8

Ben sadece burada Enumerable.Concatuzantısı yöntemi dışında, Enumerable.Append.NET Core 1.1.1 adlı başka bir yöntem var gibi görünüyor . İkincisi, tek bir öğeyi mevcut bir diziye birleştirmenizi sağlar. Böylece Aamol'un yanıtı şu şekilde de yazılabilir:

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

Yine de, bu işlevin giriş sırasını değiştirmeyeceğini lütfen unutmayın, verilen sırayı ve ekteki öğeyi bir araya getiren bir sarıcı döndürür.


4

Diğerleri, bir öğeye neden ekleyemediğiniz (ve yapmamanız gerektiği!) Hakkında zaten büyük açıklamalar yapmışlardır IEnumerable. Sadece bir koleksiyonu temsil eden ve bir ekleme yöntemi isteyen bir arabirime kodlamaya devam etmek istiyorsanız, ICollectionya da kodlamanız gerektiğini ekleyeceğim IList. Ek bir bonanza olarak, bu arayüzler uygulanır IEnumerable.


1

Bunu yapabilirsiniz.

//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;

8
Bu soru 5 yıl önce daha eksiksiz bir şekilde cevaplandı. Kabul edilen cevaba bir soruya iyi bir cevap örneği olarak bakın.
Kasım'ta pquest

1
Son satır: items = (IEnumerable <T>) listesi;
Belen Martin

0

Bunu yapmanın en kolay yolu basitçe

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

Sonra listeyi IEnumerable olarak döndürebilirsiniz çünkü IEnumerable arabirimini uygular


0

Eğer gibi nesne oluşturursanız IEnumerable<int> Ones = new List<int>(); Sonra yapabilirsiniz((List<int>)Ones).Add(1);


-8

Elbette, (T işinizi bir kenara bırakıyorum):

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

    return list.Select(i => i);
}
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.