コレクションへのAddRange


108

同僚から本日、コレクションに範囲を追加する方法を尋ねられました。彼はから継承するクラスを持っていCollection<T>ます。そのタイプのget-onlyプロパティがあり、すでにいくつかのアイテムが含まれています。別のコレクションのアイテムをプロパティコレクションに追加したいと考えています。どのようにしてC#3フレンドリーな方法でこれを行うことができますか (取得専用プロパティに関する制約に注意してください。これにより、Unionの実行や再割り当てなどの解決策が妨げられます。)

確かに、プロパティのforeachです。追加は機能します。ただし、- List<T>スタイルのAddRangeの方がはるかにエレガントです。

拡張メソッドを書くのは簡単です:

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

でも、ホイールを再発明しているような気がします。System.Linqまたはmorelinqで類似するものは見つかりませんでした

悪いデザイン?追加を呼び出すだけですか?明らかなものがない?


5
LINQからのQは「クエリ」であり、実際にはデータの取得、射影、変換などに関するものであることに注意してください。既存のコレクションの変更は、実際にはLINQの意図した目的の領域には該当しないため、LINQは何も提供しません。このための箱の。しかし、拡張メソッド(特にサンプル)がこれに理想的です。
Levi

1つの問題ICollection<T>は、Addメソッドがないようです。msdn.microsoft.com/en-us/library/… ただしCollection<T>、1つあります。
Tim Goodman

@TimGoodman-これは非ジェネリックインターフェイスです。msdn.microsoft.com/en-us/library/92t2ye13.aspx
TrueWill

「既存のコレクションを変更することは、実際にはLINQの意図する目的の領域には該当しません」。@リーバイそれでは、なぜAdd(T item)そもそも持っているのですか?単一の項目を追加する機能を提供し、すべての呼び出し元が一度に複数を追加するために反復することを期待する中途半端なアプローチのようです。あなたの発言は確かに当てはまりますがIEnumerable<T>、私はICollections何度も不満を感じています。私はあなたと意見を異にしません。
akousmata

回答:


62

いいえ、これは完全に妥当なようです。基本的にこれを行うList<T>.AddRange()メソッドがありますが、コレクションは具象である必要がありますList<T>


1
ありがとう。非常に真実ですが、ほとんどのパブリックプロパティはMSガイドラインに従っており、リストではありません。
TrueWill

7
うん-私はこれを行うことに問題がないと思う理由の根拠としてより多くを与えていました。List <T>バージョンよりも効率が悪いことを理解してください(list <T>は事前に割り当てることができるため)
Reed Copsey

この問題に示すように、.NET Core 2.2のAddRangeメソッドが誤って使用されると奇妙な動作を示す可能性があることに注意してください:github.com/dotnet/core/issues/2667
Bruno

36

ループを実行する前に、拡張メソッドでリストにキャストしてみてください。これにより、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
asオペレータは、スローすることはありません。destinationキャストできない場合はlistnullになり、elseブロックが実行されます。
rymdsmurf 2014年

4
あっ!聖なるものすべての愛のために、条件分岐を交換してください!
nicodemus13

13
私は実際には真剣です。主な理由は、それが余分な認知的負荷であることです。あなたは常に否定的な条件を評価しようとしていますが、これは通常比較的難しいですが、どちらにしても両方の分岐があります。「nullの場合」、「他の場合」は、反対ではなく(IMO)と言う方が簡単です。それはまたデフォルトについてです、それらは可能な限りポジティブな概念であるべきです、例えば.eg `if(!thing.IsDisabled){} else {} 'は停止して考えることを要求します'ああ、無効ではないということは有効であるという意味ですそれを得たので、他のブランチはそれが無効になっているときです)。解析が難しい。
nicodemus13

13
「something!= null」を解釈することは、「something == null」を解釈することよりも難しくありません。ただし、否定演算子は完全に異なるものであり、最後の例ではif-else-statementを書き換えるとその演算子が省略されます。これは客観的には改善ですが、元の質問とは関係ありません。その特定のケースでは、2つの形式は個人的な好みの問題であり、上記の理由から、私は "!="演算子を好むでしょう。
rymdsmurf

15
パターンマッチングはみんなを幸せにします... ;-)if (destination is List<T> list)
Jacob Foshee

28

以来.NET4.5あなたはワンライナーをしたい場合は、使用できるSystem.Collections.GenericのForEachを。

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

またはさらに短く

source.ForEach(destination.Add);

パフォーマンスに関しては、各ループの場合と同じです(構文上の砂糖)。

また、次のように割り当てようとしないでください

var x = source.ForEach(destination.Add) 

原因ForEachは無効です。

編集:ForEachに関するコメント、Lipertの意見からコピー


9
個人的に私はこの1つ上のリペットでよ:blogs.msdn.com/b/ericlippert/archive/2009/05/18/...
TrueWill

1
source.ForEach(destination.Add)にする必要がありますか?
フランク

4
ForEachでのみ定義されているようですがList<T>、ではありませんCollectionか?
プロテクター1



19

それぞれAddがコレクションの容量をチェックし、必要に応じて(低速で)サイズを変更することに注意してください。を使用するAddRangeと、コレクションに容量が設定され、アイテムが追加されます(高速)。この拡張方法は非常に遅くなりますが、機能します。


3
これに追加するには、AddRangeによる1つの一括通知ではなく、追加ごとにコレクション変更通知があります。
Nick Udell 2014

3

これは、もう少し高度な製品版です。

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

rymdsmurfの答えは単純すぎるように見えるかもしれませんが、異種のリストで機能します。このコードでこのユースケースをサポートすることは可能ですか?
richardsonwtr

例:destinationShape、抽象クラスのリストです。継承されたクラスのsourceリストですCircle
richardsonwtr

1

C5ジェネリックコレクションライブラリのクラスは、すべてのサポートAddRange方法を。C5には、基礎となる実装のすべての機能を実際に公開するはるかに堅牢なインターフェースがあり、System.Collections.Generic ICollectionおよびIListインターフェースと互換性があります。つまり、C5のコレクションは、基礎となる実装として簡単に置き換えることができます。


0

IEnumerable範囲をリストに追加してから、ICollection =をリストに設定できます。

        IEnumerable<T> source;

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

        ICollection<item> destination = list;

3
これは機能的に動作しますが、それだけで(読みコレクションのプロパティを作るために、Microsoftのガイドラインを破るmsdn.microsoft.com/en-us/library/ms182327.aspx
ニック・Udell

0

または、次のようにICollection拡張機能を作成することもできます。

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

        return @this;
    }

それを使用するのは、リストでそれを使用するのと同じです。

collectionA.AddRange(IEnumerable<object> items);
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.