AddRange vào bộ sưu tập


109

Hôm nay, một đồng nghiệp đã hỏi tôi cách thêm một phạm vi vào một bộ sưu tập. Anh ta có một lớp kế thừa từ Collection<T>. Có một thuộc tính chỉ nhận của loại đó đã chứa một số mục. Anh ta muốn thêm các mục trong bộ sưu tập khác vào bộ sưu tập tài sản. Làm thế nào anh ta có thể làm như vậy theo cách thân thiện với C # 3? (Lưu ý ràng buộc về thuộc tính chỉ nhận, điều này ngăn cản các giải pháp như thực hiện Union và chỉ định lại.)

Chắc chắn, một bước trước với Tài sản. Thêm sẽ hoạt động. Nhưng List<T>AddRange kiểu sẽ thanh lịch hơn nhiều.

Thật dễ dàng để viết một phương thức mở rộng:

public static class CollectionHelpers
{
    public static void AddRange<T>(this ICollection<T> destination,
                                   IEnumerable<T> source)
    {
        foreach (T item in source)
        {
            destination.Add(item);
        }
    }
}

Nhưng tôi có cảm giác mình đang phát minh lại bánh xe. Tôi không tìm thấy bất kỳ điều gì tương tự trong System.Linqhoặc morelinq .

Thiết kế tồi? Chỉ cần gọi Thêm? Thiếu rõ ràng?


5
Hãy nhớ rằng Q từ LINQ là 'truy vấn' và thực sự là về truy xuất dữ liệu, phép chiếu, chuyển đổi, v.v. Việc sửa đổi các bộ sưu tập hiện có thực sự không nằm trong mục đích dự kiến ​​của LINQ, đó là lý do tại sao LINQ không cung cấp bất cứ điều gì- của-hộp cho điều này. Nhưng các phương pháp mở rộng (và cụ thể là mẫu của bạn) sẽ lý tưởng cho việc này.
Levi

Một vấn đề, ICollection<T>dường như không có Addphương pháp. msdn.microsoft.com/en-us/library/… Tuy nhiên Collection<T>có một.
Tim Goodman,

@TimGoodman - Đó là giao diện không chung chung. Xem msdn.microsoft.com/en-us/library/92t2ye13.aspx
TrueWill

"Việc sửa đổi các bộ sưu tập hiện có thực sự không nằm trong mục đích của LINQ". @Levi Vậy tại sao lại có ngay Add(T item)từ đầu? Có vẻ như một cách tiếp cận nửa vời để cung cấp khả năng thêm một mục và sau đó mong đợi tất cả người gọi lặp lại để thêm nhiều mục cùng một lúc. Câu nói của bạn chắc chắn đúng IEnumerable<T>nhưng tôi đã thấy mình thất vọng ICollectionshơn một lần. Tôi không đồng ý với bạn, chỉ là trút giận.
akousmata

Câu trả lời:


62

Không, điều này có vẻ hoàn toàn hợp lý. Có một phương thức List<T>.AddRange () về cơ bản thực hiện điều này, nhưng yêu cầu bộ sưu tập của bạn phải cụ thể List<T>.


1
Cảm ơn; rất đúng, nhưng hầu hết các thuộc tính công cộng tuân theo các nguyên tắc của MS và không phải là Danh sách.
TrueWill

7
Vâng - tôi đang đưa ra lý do chính đáng hơn cho lý do tại sao tôi không nghĩ rằng có vấn đề gì khi làm việc này. Chỉ cần nhận ra nó sẽ ít hiệu quả hơn so với List <T> phiên bản (kể từ khi danh sách <T> có thể trước khi phân bổ)
Reed Copsey

Chỉ cần lưu ý rằng phương thức AddRange trong .NET Core 2.2 có thể hiển thị hành vi kỳ lạ nếu được sử dụng không đúng cách, như được hiển thị trong sự cố này: github.com/dotnet/core/issues/2667
Bruno

36

Thử truyền sang Danh sách trong phương thức mở rộng trước khi chạy vòng lặp. Bằng cách đó, bạn có thể tận dụng hiệu suất của List.AddRange.

public static void AddRange<T>(this ICollection<T> destination,
                               IEnumerable<T> source)
{
    List<T> list = destination as List<T>;

    if (list != null)
    {
        list.AddRange(source);
    }
    else
    {
        foreach (T item in source)
        {
            destination.Add(item);
        }
    }
}

2
Người asđiều hành sẽ không bao giờ ném. Nếu destinationkhông thể ép kiểu, listsẽ là null và elsekhối sẽ thực thi.
rymdsmurf

4
arrgggh! Trao đổi các nhánh điều kiện, vì tình yêu của tất cả những gì thánh thiện!
nicodemus

13
Tôi thực sự nghiêm túc, lý do chính là nó quá tải về nhận thức, điều này thường thực sự khá khó khăn. Bạn liên tục cố gắng đánh giá các điều kiện tiêu cực, điều này thường tương đối khó, dù sao thì bạn cũng có cả hai nhánh, sẽ dễ dàng hơn (IMO) để nói 'if null' làm điều này, 'else' làm điều này, thay vì ngược lại. Đó cũng là về mặc định, chúng nên là khái niệm tích cực thường xuyên nhất có thể, ví dụ: if (! Thing.IsDisabled) {} ​​else {} 'yêu cầu bạn dừng lại và suy nghĩ' à, không phải là tắt nghĩa là được bật, đúng không, đã hiểu, vì vậy nhánh khác là khi nó bị vô hiệu hóa). Khó phân tích cú pháp.
nicodemus

13
Việc diễn giải "something! = Null" không khó hơn việc diễn giải "something == null". Tuy nhiên, toán tử phủ định là một thứ hoàn toàn khác và trong ví dụ cuối cùng của bạn, việc viết lại câu lệnh if-else-sẽ tổng hợp toán tử đó. Đó là một cải tiến về mặt khách quan, nhưng là một cải tiến không liên quan đến câu hỏi ban đầu. Trong trường hợp cụ thể đó, hai biểu mẫu là vấn đề của sở thích cá nhân và tôi thích toán tử "! =" -, dựa trên lý do ở trên.
rymdsmurf

15
Mô hình kết hợp sẽ làm cho tất cả mọi người hạnh phúc ... ;-)if (destination is List<T> list)
Jacob Foshee

28

.NET4.5nếu bạn muốn một lớp lót, bạn có thể sử dụng System.Collections.GenericForEach.

source.ForEach(o => destination.Add(o));

hoặc thậm chí ngắn hơn như

source.ForEach(destination.Add);

Về hiệu suất, nó giống như đối với mỗi vòng lặp (đường cú pháp).

Cũng đừng thử gán nó như

var x = source.ForEach(destination.Add) 

nguyên nhân ForEachlà vô hiệu.

Chỉnh sửa: Sao chép từ nhận xét, ý kiến ​​của Lipert trên ForEach


9
Cá nhân tôi với Lippert trên một này: blogs.msdn.com/b/ericlippert/archive/2009/05/18/...
TrueWill

1
Nó có phải là source.ForEach (destination.Add) không?
Frank

4
ForEachdường như chỉ được xác định trên List<T>, không Collection?
Người bảo vệ một

Lippert bây giờ có thể được tìm thấy tại web.archive.org/web/20190316010649/https://...
user7610

Liên kết cập nhật đến bài đăng trên blog của Eric Lippert: Những cuộc phiêu lưu tuyệt vời trong mã hóa | “Foreach” vs “ForEach”
Alexander

19

Hãy nhớ rằng mỗi cái Addsẽ kiểm tra dung lượng của bộ sưu tập và thay đổi kích thước bất cứ khi nào cần thiết (chậm hơn). Với AddRange, bộ sưu tập sẽ được thiết lập dung lượng và sau đó thêm các mục (nhanh hơn). Phương thức mở rộng này sẽ cực kỳ chậm, nhưng sẽ hoạt động.


3
Để thêm vào điều này, cũng sẽ có một thông báo thay đổi bộ sưu tập cho mỗi lần bổ sung, trái ngược với một thông báo hàng loạt với AddRange.
Nick Udell

3

Đây là một phiên bản nâng cao / sẵn sàng cho sản xuất:

    public static class CollectionExtensions
    {
        public static TCol AddRange<TCol, TItem>(this TCol destination, IEnumerable<TItem> source)
            where TCol : ICollection<TItem>
        {
            if(destination == null) throw new ArgumentNullException(nameof(destination));
            if(source == null) throw new ArgumentNullException(nameof(source));

            // don't cast to IList to prevent recursion
            if (destination is List<TItem> list)
            {
                list.AddRange(source);
                return destination;
            }

            foreach (var item in source)
            {
                destination.Add(item);
            }

            return destination;
        }
    }

Câu trả lời của rymdsmurf có thể trông ngây thơ, quá đơn giản, nhưng nó hoạt động với các danh sách không đồng nhất. Có thể làm cho mã này hỗ trợ trường hợp sử dụng này không?
richardsonwtr

Eg: destinationlà một danh sách Shape, một lớp trừu tượng. sourcelà một danh sách Circle, một lớp kế thừa.
richardsonwtr

1

Các C5 Generic Bộ sưu tập Thư viện các lớp học đều ủng hộ AddRangephương pháp. C5 có giao diện mạnh mẽ hơn nhiều, thực sự hiển thị tất cả các tính năng của các triển khai cơ bản của nó và tương thích với giao diện System.Collections.Generic ICollectionIListgiao diện, có nghĩa là C5các tập hợp của có thể dễ dàng thay thế như triển khai cơ bản.


0

Bạn có thể thêm phạm vi IEnumerable của mình vào một danh sách sau đó đặt ICollection = vào danh sách.

        IEnumerable<T> source;

        List<item> list = new List<item>();
        list.AddRange(source);

        ICollection<item> destination = list;

3
Trong khi điều này hoạt động về mặt chức năng, nó phá vỡ các nguyên tắc của Microsoft để làm cho các thuộc tính của bộ sưu tập ở chế độ chỉ đọc ( msdn.microsoft.com/en-us/library/ms182327.aspx )
Nick Udell 22/09/14

0

Hoặc bạn chỉ có thể tạo một phần mở rộng ICollection như sau:

 public static ICollection<T> AddRange<T>(this ICollection<T> @this, IEnumerable<T> items)
    {
        foreach(var item in items)
        {
            @this.Add(item);
        }

        return @this;
    }

Sử dụng nó sẽ giống như sử dụng nó trong một danh sách:

collectionA.AddRange(IEnumerable<object> items);
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.