Bagaimana saya bisa menambahkan item ke koleksi <T> IEnumerable?

405

Pertanyaan saya sebagai judul di atas. Sebagai contoh,

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

tapi bagaimanapun juga hanya ada 1 item di dalamnya.

Bisakah kita memiliki metode seperti items.Add(item)?

seperti List<T>

Lihat sekarang
sumber
4
IEnumerable<T>dimaksudkan untuk menanyakan koleksi saja. Ini adalah tulang punggung kerangka kerja LINQ. Ini selalu merupakan abstraksi dari beberapa koleksi lain seperti Collection<T>, List<T>, atau Array. Antarmuka hanya menyediakan GetEnumeratormetode yang mengembalikan instance IEnumerator<T>yang memungkinkan berjalannya koleksi yang diperluas satu per satu. Metode ekstensi LINQ dibatasi oleh antarmuka ini. IEnumerable<T>dirancang untuk dibaca hanya karena dapat mewakili agregasi bagian dari beberapa koleksi.
Jordan
3
Untuk contoh ini, nyatakan saja IList<T>alih-alih IEnumerable<T>:IList<T> items = new T[]{new T("msg")}; items.Add(new T("msg2"));
NightOwl888

Jawaban:

480

Anda tidak bisa, karena IEnumerable<T>tidak selalu mewakili koleksi yang itemnya dapat ditambahkan. Bahkan, itu tidak selalu mewakili koleksi sama sekali! Sebagai contoh:

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??

Apa yang dapat Anda lakukan, bagaimanapun, adalah membuat objek baru IEnumerable (dari tipe yang tidak ditentukan), yang, ketika disebutkan, akan menyediakan semua item dari yang lama, ditambah beberapa milik Anda sendiri. Anda gunakan Enumerable.Concatuntuk itu:

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

Ini tidak akan mengubah objek array (toh, Anda tidak bisa memasukkan item ke array). Tapi itu akan membuat objek baru yang akan mendaftar semua item dalam array, dan kemudian "Foo". Selain itu, objek baru itu akan melacak perubahan dalam array (yaitu, setiap kali Anda menghitungnya, Anda akan melihat nilai item saat ini).

Pavel Minaev
sumber
3
Membuat array untuk menambah nilai tunggal agak tinggi pada overhead. Ini sedikit lebih dapat digunakan kembali untuk mendefinisikan metode ekstensi untuk satu nilai Concat.
JaredPar
4
Itu tergantung - mungkin sepadan, tapi saya tergoda untuk menyebutnya optimasi dini kecuali Concatdipanggil berulang kali; dan jika ya, Anda memiliki masalah yang lebih besar, karena setiap kali Anda Concat, Anda mendapatkan satu lapis enumerator lagi - jadi pada akhirnya itu satu panggilan untuk MoveNextakan menghasilkan rantai panggilan yang sama panjangnya dengan jumlah iterasi.
Pavel Minaev
2
@Pavel, heh. Biasanya bukan memori overhead untuk menciptakan array yang mengganggu saya. Ini adalah pengetikan ekstra yang menurut saya menjengkelkan :).
JaredPar
2
Jadi saya mengerti sedikit lebih banyak tentang apa yang dilakukan IEnumerable. Seharusnya hanya tampilan yang tidak ingin diubah. Terima kasih teman
ldsenow
7
Aku punya permintaan fitur Connect untuk sintaksis gula untuk IEnumerable<T>di C #, termasuk overloading operator+seperti Concat(by the way, apakah Anda tahu bahwa dalam VB, Anda dapat indeks IEnumerableseolah-olah itu sebuah array - menggunakan ElementAtbawah tenda): connect.microsoft.com / VisualStudio / umpan balik / ... . Jika kita juga mendapatkan dua kelebihan Concatuntuk mengambil satu item baik di kiri dan di kanan, ini akan mengurangi pengetikan ke xs +=x - jadi tusuk Mads (Torgersen) untuk memprioritaskan ini dalam C # 5.0 :)
Pavel Minaev
98

Jenis IEnumerable<T>ini tidak mendukung operasi semacam itu. Tujuan IEnumerable<T>antarmuka adalah untuk memungkinkan konsumen untuk melihat konten koleksi. Tidak mengubah nilai.

Ketika Anda melakukan operasi seperti .ToList (). Add () Anda membuat yang baru List<T>dan menambahkan nilai ke daftar itu. Tidak memiliki koneksi ke daftar asli.

Yang dapat Anda lakukan adalah menggunakan metode Tambahkan ekstensi untuk membuat yang baru IEnumerable<T>dengan nilai tambah.

items = items.Add("msg2");

Bahkan dalam kasus ini tidak akan memodifikasi IEnumerable<T>objek asli . Ini dapat diverifikasi dengan memegang referensi untuk itu. Sebagai contoh

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

Setelah rangkaian operasi ini, temp variabel akan tetap hanya merujuk enumerable dengan elemen tunggal "foo" di set nilai sementara item akan merujuk enumerable berbeda dengan nilai "foo" dan "bar".

EDIT

Saya terus-menerus lupa bahwa Add bukan metode ekstensi biasa IEnumerable<T>karena itu adalah salah satu yang pertama yang akhirnya saya definisikan. Ini dia

public static IEnumerable<T> Add<T>(this IEnumerable<T> e, T value) {
  foreach ( var cur in e) {
    yield return cur;
  }
  yield return value;
}
JaredPar
sumber
12
Itu disebut Concat:)
Pavel Minaev
3
@Pavel, terima kasih sudah menunjukkannya. Bahkan Concat tidak akan berfungsi karena butuh IEnumerable <T> lainnya. Saya menempel metode ekstensi khas yang saya tetapkan dalam proyek saya.
JaredPar
10
Saya tidak akan menyebutnya demikian Add, karena Addpada hampir semua jenis NET lainnya. (Bukan hanya koleksi) bermutasi koleksi di tempat. Mungkin With? Atau bahkan bisa jadi hanya kelebihan beban Concat.
Pavel Minaev
4
Saya setuju Concatoverload mungkin akan menjadi pilihan yang lebih baik karena Addbiasanya menyiratkan kemampuan berubah-ubah.
Dan Abramov
15
Bagaimana dengan memodifikasi bodi return e.Concat(Enumerable.Repeat(value,1));?
Roy Tinker
58

Sudahkah Anda mempertimbangkan untuk menggunakan ICollection<T>atau IList<T>antarmuka, mereka ada karena alasan yang Anda ingin memiliki Addmetode pada IEnumerable<T>.

IEnumerable<T>digunakan untuk 'menandai' suatu jenis sebagai ... yah, dapat dihitung atau hanya urutan item tanpa harus membuat jaminan apakah objek yang sebenarnya mendasari mendukung penambahan / penghapusan item. Juga ingat bahwa antarmuka ini diimplementasikan IEnumerable<T>sehingga Anda mendapatkan semua metode ekstensi yang Anda dapatkan IEnumerable<T>juga.

Abhijeet Patel
sumber
31

Pasangan pendek, metode ekstensi manis IEnumerabledan IEnumerable<T>lakukan untuk saya:

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

Elegan (well, kecuali untuk versi non-generik). Sayang sekali metode ini tidak ada dalam BCL.


sumber
Metode Prepend pertama memberi saya kesalahan. "'object []' tidak mengandung definisi untuk 'Concat' dan metode ekstensi terbaik membebani 'System.Linq.Enumerable.Compat <TSource> (System.Collections.Generic.IEnumerable <TSource>, System.Collections.Generic. IEnumerable <TSource>) 'memiliki beberapa argumen yang tidak valid "
Tim Newton
@TimNewton Anda salah melakukannya. Siapa yang tahu mengapa, karena tidak ada yang tahu dari potongan kode kesalahan itu. Protip: selalu tangkap pengecualian dan lakukan ToString()di atasnya, karena itu menangkap lebih banyak detail kesalahan. Saya kira Anda tidak include System.Linq;di bagian atas file Anda, tetapi siapa yang tahu? Cobalah mengatasinya sendiri, buat prototipe minimum yang menunjukkan kesalahan yang sama jika Anda tidak bisa dan kemudian minta bantuan dalam sebuah pertanyaan.
Saya baru saja menyalin dan menempelkan kode di atas. Semuanya bekerja ok kecuali Prepend yang memberikan kesalahan yang saya sebutkan. Ini bukan pengecualian runtime yang merupakan pengecualian waktu kompilasi.
Tim Newton
@TimNewton: Saya tidak tahu apa yang Anda bicarakan berfungsi dengan baik, Anda jelas keliru mengabaikan perubahan dalam penyuntingan sekarang saya harus dalam perjalanan, selamat siang, tuan.
mungkin ini sesuatu yang berbeda dengan lingkungan kita. Saya menggunakan VS2012 dan .NET 4.5. Jangan buang waktu lagi untuk ini, saya hanya berpikir saya akan menunjukkan bahwa ada masalah dengan kode di atas, meskipun kecil. Bagaimanapun saya telah memutakhirkan jawaban ini karena ini berguna. Terima kasih.
Tim Newton
22

Tidak, IEnumerable tidak mendukung penambahan item ke dalamnya.

'Alternatif' Anda adalah:

var List = new List(items);
List.Add(otherItem);
Paul van Brenk
sumber
4
Saran Anda bukan satu-satunya alternatif. Sebagai contoh, ICollection dan IList keduanya merupakan opsi yang masuk akal di sini.
jason
6
Tapi kamu juga tidak bisa instantiate.
Paul van Brenk
16

Untuk menambahkan pesan kedua, Anda perlu -

IEnumerable<T> items = new T[]{new T("msg")};
items = items.Concat(new[] {new T("msg2")})
Aamol
sumber
1
Tetapi Anda juga perlu menetapkan hasil metode Concat ke objek item.
Tomasz Przychodzki
1
Ya, Tomasz kamu benar. Saya telah memodifikasi ekspresi terakhir untuk menetapkan nilai gabungan ke item.
Aamol
8

Anda tidak hanya tidak dapat menambahkan item seperti yang Anda nyatakan, tetapi jika Anda menambahkan item ke List<T>(atau hampir semua koleksi non-baca saja) yang Anda miliki untuk enumerator yang ada, enumerator tidak valid (melempar InvalidOperationExceptionsejak saat itu).

Jika Anda menggabungkan hasil dari beberapa jenis kueri data, Anda dapat menggunakan Concatmetode ekstensi:

Sunting: Saya awalnya menggunakan Unionekstensi dalam contoh, yang tidak terlalu benar. Aplikasi saya menggunakannya secara luas untuk memastikan pertanyaan yang tumpang tindih tidak menduplikasi hasil.

IEnumerable<T> itemsA = ...;
IEnumerable<T> itemsB = ...;
IEnumerable<T> itemsC = ...;
return itemsA.Concat(itemsB).Concat(itemsC);
Sam Harwell
sumber
8

Saya hanya datang ke sini untuk mengatakan bahwa, selain dari Enumerable.Concatmetode ekstensi, tampaknya ada metode lain bernama Enumerable.Append.NET Core 1.1.1. Yang terakhir memungkinkan Anda untuk menggabungkan satu item ke urutan yang ada. Jadi jawaban Aamol juga bisa ditulis sebagai

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

Namun, harap dicatat bahwa fungsi ini tidak akan mengubah urutan input, itu hanya mengembalikan pembungkus yang menempatkan urutan yang diberikan dan item yang ditambahkan bersama-sama.

CXuesong
sumber
4

Orang lain telah memberikan penjelasan yang bagus tentang mengapa Anda tidak dapat (dan seharusnya tidak!) Dapat menambahkan item ke IEnumerable. Saya hanya akan menambahkan bahwa jika Anda ingin melanjutkan pengkodean ke antarmuka yang mewakili koleksi dan ingin metode add, Anda harus kode ke ICollectionatau IList. Sebagai tambahan, antarmuka ini mengimplementasikan IEnumerable.

jason
sumber
1

kamu bisa melakukan ini.

//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;
Lrodriguez84
sumber
8
Pertanyaan ini dijawab dengan cara yang lebih lengkap 5 tahun yang lalu. Lihatlah jawaban yang diterima sebagai contoh jawaban yang baik untuk suatu pertanyaan.
pquest
1
Baris terakhir adalah: items = (IEnumerable <T>) daftar;
Belen Martin
0

Cara termudah untuk melakukannya adalah sederhana

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

Kemudian Anda dapat mengembalikan daftar sebagai IEnumerable juga karena mengimplementasikan antarmuka IEnumerable

Juha Sinkkonen
sumber
0

Jika Anda membuat objek IEnumerable<int> Ones = new List<int>(); Anda maka Anda dapat melakukannya((List<int>)Ones).Add(1);

Erik Hosszú
sumber
-8

Tentu, Anda bisa (saya mengesampingkan bisnis-T Anda):

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

    return list.Select(i => i);
}
Just N Observer
sumber