Làm cách nào tôi có thể thêm một mục vào bộ sưu tập <T> của IE?


405

Câu hỏi của tôi như tiêu đề ở trên. Ví dụ,

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

nhưng sau tất cả, nó chỉ có 1 mục bên trong.

Chúng ta có thể có một phương pháp như thế items.Add(item)nào?

như List<T>


4
IEnumerable<T>chỉ dành cho truy vấn bộ sưu tập. Nó là xương sống cho khung LINQ. Nó luôn luôn là một sự trừu tượng của một số bộ sưu tập khác như Collection<T>, List<T>hoặc Array. Giao diện chỉ cung cấp một GetEnumeratorphương thức trả về một thể IEnumerator<T>hiện cho phép đi bộ của bộ sưu tập mở rộng một mục tại một thời điểm. Các phương thức mở rộng LINQ bị giới hạn bởi giao diện này. IEnumerable<T>được thiết kế để chỉ đọc bởi vì nó có thể đại diện cho một tập hợp các phần của nhiều bộ sưu tập.
Jordan

3
Trong ví dụ này, chỉ cần khai báo IList<T>thay vì IEnumerable<T>:IList<T> items = new T[]{new T("msg")}; items.Add(new T("msg2"));
NightOwl888

Câu trả lời:


480

Bạn không thể, bởi vì IEnumerable<T>không nhất thiết phải đại diện cho một bộ sưu tập mà các mục có thể được thêm vào. Trong thực tế, nó không nhất thiết phải đại diện cho một bộ sưu tập! Ví dụ:

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

Tuy nhiên, những gì bạn có thể làm là tạo một đối tượng mới IEnumerable (thuộc loại không xác định), khi được liệt kê, sẽ cung cấp tất cả các mục của đối tượng cũ, cộng với một số mục của riêng bạn. Bạn sử dụng Enumerable.Concatcho điều đó:

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

Điều này sẽ không thay đổi đối tượng mảng (dù sao bạn cũng không thể chèn các mục vào mảng). Nhưng nó sẽ tạo một đối tượng mới sẽ liệt kê tất cả các mục trong mảng và sau đó là "Foo". Hơn nữa, đối tượng mới đó sẽ theo dõi các thay đổi trong mảng (tức là bất cứ khi nào bạn liệt kê nó, bạn sẽ thấy các giá trị hiện tại của các mục).


3
Tạo một mảng để thêm một giá trị là một chút cao trên đầu. Đó là một chút có thể sử dụng lại để xác định một phương thức mở rộng cho một concat giá trị duy nhất.
JaredPar

4
Nó phụ thuộc - nó có thể có giá trị, nhưng tôi muốn gọi nó là tối ưu hóa sớm trừ khi Concatđược gọi liên tục trong một vòng lặp; và nếu có, dù sao thì bạn cũng gặp vấn đề lớn hơn, bởi vì mỗi lần bạn Concatnhận được thêm một lớp liệt kê - vì vậy đến cuối cuộc gọi, một cuộc gọi MoveNextsẽ dẫn đến một chuỗi các cuộc gọi có độ dài bằng số lần lặp.
Pavel Minaev

2
@Pavel, heh. Thông thường, nó không phải là chi phí bộ nhớ để tạo ra mảng làm phiền tôi. Đó là cách gõ thêm mà tôi thấy khó chịu :).
JaredPar

2
Vì vậy, tôi hiểu thêm một chút những gì IEnumerable làm. Nó chỉ nên được xem không có ý định sửa đổi. Cảm ơn các bạn
ldsenow

7
Tôi đã có một yêu cầu tính năng Connect cho cú pháp đường IEnumerable<T>trong C #, bao gồm cả quá tải operator+như Concat(nhân tiện, bạn có biết rằng trong VB, bạn có thể lập chỉ mục IEnumerablenhư thể đó là một mảng - nó sử dụng ElementAtdưới mui xe): connect.microsoft.com / VisualStudio / feedback / SSH . Nếu chúng ta cũng nhận thêm hai lần quá tải Concatđể lấy một mục duy nhất ở bên trái và bên phải, điều này sẽ cắt giảm việc gõ xs +=x - vì vậy hãy chọc Mads (Torgersen) để ưu tiên điều này trong C # 5.0 :)
Pavel Minaev

98

Các loại IEnumerable<T>không hỗ trợ các hoạt động như vậy. Mục đích của IEnumerable<T>giao diện là cho phép người tiêu dùng xem nội dung của bộ sưu tập. Không sửa đổi các giá trị.

Khi bạn thực hiện các thao tác như .ToList (). Add () bạn đang tạo mới List<T>và thêm một giá trị vào danh sách đó. Nó không có kết nối với danh sách ban đầu.

Những gì bạn có thể làm là sử dụng phương pháp Thêm tiện ích mở rộng để tạo mới IEnumerable<T>với giá trị gia tăng.

items = items.Add("msg2");

Ngay cả trong trường hợp này, nó sẽ không sửa đổi IEnumerable<T>đối tượng ban đầu . Điều này có thể được xác nhận bằng cách giữ một tài liệu tham khảo đến nó. Ví dụ

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

Sau tập hợp hoạt động này, temp biến vẫn sẽ chỉ tham chiếu một liệt kê với một phần tử duy nhất "foo" trong tập hợp các giá trị trong khi các mục sẽ tham chiếu một liệt kê khác với các giá trị "foo" và "bar".

BIÊN TẬP

Tôi tình cờ quên rằng Add không phải là một phương thức mở rộng điển hình IEnumerable<T>vì đó là một trong những phương thức đầu tiên mà cuối cùng tôi xác định. Nó đây rồi

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

12
Đó gọi là Concat:)
Pavel Minaev

3
@Pavel, cảm ơn vì đã chỉ ra điều đó. Ngay cả Concat sẽ không hoạt động mặc dù phải mất một <T> IE khác. Tôi đã dán phương thức mở rộng điển hình mà tôi xác định trong các dự án của mình.
JaredPar

10
AddMặc dù vậy, tôi sẽ không gọi điều đó bởi vì Addtrên hầu như bất kỳ loại .NET nào khác (không chỉ các bộ sưu tập) đều làm thay đổi bộ sưu tập tại chỗ. Có lẽ Withnào? Hoặc thậm chí có thể chỉ là quá tải khác Concat.
Pavel Minaev

4
Tôi đồng ý Concatquá tải có lẽ sẽ là một lựa chọn tốt hơn vì Addthường ngụ ý tính đột biến.
Dan Abramov

15
Điều gì về việc sửa đổi cơ thể để return e.Concat(Enumerable.Repeat(value,1));?
Roy Tinker

58

Bạn đã xem xét việc sử dụng ICollection<T>hoặc IList<T>giao diện thay vào đó, chúng tồn tại vì lý do mà bạn muốn có một Addphương thức trên IEnumerable<T>.

IEnumerable<T>được sử dụng để 'đánh dấu' một loại là ... tốt, có thể đếm được hoặc chỉ là một chuỗi các mục mà không nhất thiết phải đảm bảo liệu đối tượng thực sự bên dưới có hỗ trợ thêm / xóa các mục hay không. Cũng nên nhớ rằng các giao diện này triển khai IEnumerable<T>để bạn có được tất cả các phương thức tiện ích mở rộng mà bạn có được IEnumerable<T>.


31

Một vài phương pháp mở rộng ngắn và ngọt ngào trên IEnumerableIEnumerable<T>làm điều đó cho tôi:

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

Elegant (tốt, ngoại trừ các phiên bản không chung chung). Quá tệ những phương pháp này không có trong BCL.


Phương pháp Prepend đầu tiên là cho tôi một lỗi. "'object []' không chứa định nghĩa cho 'concat' và quá tải phương thức mở rộng tốt nhất 'System.Linq.Enumerable.Concat <TSource> (System.Collections.Generic.IEnumerable <TSource>, System.Collections.Generic. IEnumerable <TSource>) 'có một số đối số không hợp lệ "
Tim Newton

@TimNewton bạn đang làm sai. Ai biết tại sao, như không ai có thể nói từ đoạn mã lỗi đó. Protip: luôn luôn bắt ngoại lệ và thực hiện ToString()trên đó, vì điều đó nắm bắt được nhiều chi tiết lỗi hơn. Tôi đoán bạn đã không include System.Linq;ở đầu tập tin của bạn, nhưng ai biết? Hãy tự mình tìm ra nó, tạo một nguyên mẫu tối thiểu có lỗi tương tự nếu bạn không thể và sau đó yêu cầu trợ giúp trong một câu hỏi.

Tôi chỉ sao chép và dán mã ở trên. Tất cả đều hoạt động tốt, ngoại trừ Chuẩn bị đưa ra lỗi tôi đã đề cập. Nó không phải là một ngoại lệ thời gian chạy, nó là một ngoại lệ thời gian biên dịch.
Tim Newton

@TimNewton: Tôi không biết những gì bạn đang nói về nó hoạt động hoàn hảo, rõ ràng là bạn đang nhầm lẫn bỏ qua những thay đổi trong chỉnh sửa bây giờ tôi phải trên đường, chúc một ngày tốt lành, thưa ông.

có lẽ nó là một cái gì đó khác nhau về môi trường của chúng tôi. Tôi đang sử dụng VS2012 và .NET 4.5. Đừng lãng phí thời gian cho việc này nữa, tôi chỉ nghĩ rằng tôi sẽ chỉ ra rằng có một vấn đề với đoạn mã trên, mặc dù là một lỗi nhỏ. Dù sao tôi cũng đã đưa ra câu trả lời này vì nó hữu ích. cảm ơn.
Tim Newton


22

Không có IEnumerable không hỗ trợ thêm các mục vào nó.

Bạn 'thay thế' là:

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

4
Đề nghị của bạn không phải là sự thay thế duy nhất. Ví dụ, ICollection và IList đều là những lựa chọn hợp lý ở đây.
jason

6
Nhưng bạn cũng không thể khởi tạo.
Paul van Brenk

16

Để thêm tin nhắn thứ hai, bạn cần phải -

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

1
Nhưng bạn cũng cần gán kết quả của phương thức Concat cho đối tượng item.
Tomasz Przychodzki

1
Vâng, bạn đã đúng. Tôi đã sửa đổi biểu thức cuối cùng để gán các giá trị được nối cho các mục.
Aamol

8

Bạn không chỉ không thể thêm các mục như bạn nêu, mà nếu bạn thêm một mục vào một List<T>(hoặc khá nhiều bất kỳ bộ sưu tập không đọc nào khác) mà bạn có một điều tra viên hiện có, thì điều tra viên bị vô hiệu (ném InvalidOperationExceptiontừ đó trở đi).

Nếu bạn đang tổng hợp kết quả từ một số loại truy vấn dữ liệu, bạn có thể sử dụng Concatphương pháp tiện ích mở rộng:

Chỉnh sửa: Ban đầu tôi đã sử dụng Unionphần mở rộng trong ví dụ, điều này không thực sự chính xác. Ứng dụng của tôi sử dụng nó rộng rãi để đảm bảo các truy vấn chồng chéo không trùng lặp kết quả.

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

8

Tôi chỉ đến đây để nói rằng, ngoài Enumerable.Concatphương thức mở rộng, dường như còn có một phương thức khác có tên Enumerable.Appendtrong .NET Core 1.1.1. Cái sau cho phép bạn nối một mục duy nhất với một chuỗi hiện có. Vì vậy, câu trả lời của Aamol cũng có thể được viết là

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

Tuy nhiên, xin lưu ý rằng chức năng này sẽ không thay đổi trình tự đầu vào, nó chỉ trả về một trình bao bọc đặt trình tự đã cho và mục được nối với nhau.


4

Những người khác đã đưa ra những lời giải thích tuyệt vời về lý do tại sao bạn không thể (và không nên!) Có thể thêm các mục vào một IEnumerable. Tôi sẽ chỉ thêm rằng nếu bạn đang muốn tiếp tục mã hóa vào một giao diện đại diện cho một bộ sưu tập và muốn một phương thức thêm, bạn nên mã hóa thành ICollectionhoặc IList. Như một bonanza được thêm vào, các giao diện này thực hiện IEnumerable.


1

bạn có thể làm được việc này.

//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
Câu hỏi này đã được trả lời một cách đầy đủ hơn 5 năm trước. Nhìn vào câu trả lời được chấp nhận như một ví dụ về một câu trả lời hay cho một câu hỏi.
cuộc thi

1
Dòng cuối cùng là: items = (IEnumerable <T>) list;
Belen Martin

0

Cách dễ nhất để làm điều đó chỉ đơn giản là

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

Sau đó, bạn có thể trả về danh sách dưới dạng IEnumerable vì nó thực hiện giao diện IEnumerable


0

Nếu bạn tạo đối tượng của mình là IEnumerable<int> Ones = new List<int>(); Then thì bạn có thể làm((List<int>)Ones).Add(1);


-8

Chắc chắn, bạn có thể (tôi sẽ bỏ doanh nghiệp T của bạn sang một bên):

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

    return list.Select(i => i);
}
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.