web-dev-qa-db-ja.com

コレクションからアイテムを削除する最良の方法

C#でコレクションからアイテムを削除するのに最適な方法は何ですか?アイテムは既知ですが、インデックスではありません。これはそれを行う1つの方法ですが、せいぜいエレガントではないようです。

//Remove the existing role assignment for the user.
int cnt = 0;
int assToDelete = 0;
foreach (SPRoleAssignment spAssignment in workspace.RoleAssignments)
{
    if (spAssignment.Member.Name == shortName)
    {
        assToDelete = cnt;
    }
    cnt++;
}
workspace.RoleAssignments.Remove(assToDelete);

私が本当にやりたいのは、コレクション全体をループせずに2つの追加変数を使用せずに、プロパティ(この場合は名前)で削除するアイテムを見つけることです。

67
Dan

プロパティの1つを使用してコレクションのメンバーにアクセスする場合は、Dictionary<T> または KeyedCollection<T>代わりに。この方法では、探しているアイテムを検索する必要はありません。

そうでなければ、少なくともこれを行うことができます:

foreach (SPRoleAssignment spAssignment in workspace.RoleAssignments)
{
    if (spAssignment.Member.Name == shortName)
    {
        workspace.RoleAssignments.Remove(spAssignment);
        break;
    }
}
25
Jon B

RoleAssignmentsがList<T>の場合、次のコードを使用できます。

workSpace.RoleAssignments.RemoveAll(x =>x.Member.Name == shortName);
128
JaredPar

@smaclellは、@ sambo99へのコメントで、逆反復がより効率的である理由を尋ねました。

時々より効率的です。人々のリストがあり、信用格付けが1000未満のすべての顧客を削除またはフィルタリングするとします。

次のデータがあります

"Bob" 999
"Mary" 999
"Ted" 1000

前に繰り返すと、すぐに問題が発生します

for( int idx = 0; idx < list.Count ; idx++ )
{
    if( list[idx].Rating < 1000 )
    {
        list.RemoveAt(idx); // whoops!
    }
}

Idx = 0でBobを削除し、残りのすべての要素を左にシフトします。次回のループではidx = 1ですが、list [1]はTedではなくMaryになりました。誤ってMaryをスキップしてしまいます。 whileループを使用でき、さらに変数を導入できます。

または、単に逆の反復を行います。

for (int idx = list.Count-1; idx >= 0; idx--)
{
    if (list[idx].Rating < 1000)
    {
        list.RemoveAt(idx);
    }
}

削除されたアイテムの左側にあるすべてのインデックスは同じままなので、アイテムをスキップしません。

配列から削除するインデックスのリストが与えられた場合、同じ原則が適用されます。物事をまっすぐにするには、リストをソートしてから、最高のインデックスから最低のアイテムを削除する必要があります。

これで、Linqを使用して、何をしているのかを簡単に宣言できます。

list.RemoveAll(o => o.Rating < 1000);

単一のアイテムを削除するこのケースでは、前方または後方への反復がより効率的ではありません。これにはLinqも使用できます。

int removeIndex = list.FindIndex(o => o.Name == "Ted");
if( removeIndex != -1 )
{
    list.RemoveAt(removeIndex);
}
22
Robert Paulson

単純なリスト構造の場合、最も効率的な方法は、Predicate RemoveAll実装を使用することです。

例えば。

 workSpace.RoleAssignments.RemoveAll(x =>x.Member.Name == shortName);

その理由は次のとおりです。

  1. Predicate/Linq RemoveAllメソッドはListに実装され、実際のデータを格納する内部配列にアクセスできます。データをシフトし、内部配列のサイズを変更します。
  2. RemoveAtメソッドの実装は非常に遅く、データの基になる配列全体を新しい配列にコピーします。これは、リストに対して逆反復が役に立たないことを意味します

以前のc#3.0の時代にこれを実装することにこだわっている場合。 2つのオプションがあります。

  • メンテナンスが簡単なオプション。一致するすべてのアイテムを新しいリストにコピーし、基になるリストを交換します。

例えば。

List<int> list2 = new List<int>() ; 
foreach (int i in GetList())
{
    if (!(i % 2 == 0))
    {
        list2.Add(i);
    }
}
list2 = list2;

または

  • リスト内のすべてのデータが一致しない場合にリスト内のすべてのデータをシフトダウンしてから、配列のサイズを変更する、やや高速なオプションです。

リストからものを頻繁に削除する場合は、おそらく HashTable (.net 1.1)または Dictionary (.net 2.0)または HashSet (.net 3.5)は、この目的により適しています。

10
Sam Saffron

ICollectionの場合、RemoveAllメソッドはありません。これを行う拡張メソッドは次のとおりです。

    public static void RemoveAll<T>(this ICollection<T> source, 
                                    Func<T, bool> predicate)
    {
        if (source == null)
            throw new ArgumentNullException("source", "source is null.");

        if (predicate == null)
            throw new ArgumentNullException("predicate", "predicate is null.");

        source.Where(predicate).ToList().ForEach(e => source.Remove(e));
    }

ベース: http://phejndorf.wordpress.com/2011/03/09/a-removeall-extension-for-the-collection-class/

9
Colin

コレクションはどのタイプですか?リストの場合、役立つ「RemoveAll」を使用できます。

int cnt = workspace.RoleAssignments
                      .RemoveAll(spa => spa.Member.Name == shortName)

(これは.NET 2.0で動作します。もちろん、新しいコンパイラがない場合は、Niceの代わりに "delegate(SPRoleAssignment spa){return spa.Member.Name == shortName;}"を使用する必要があります。ラムダ構文。)

リストではなく、ICollectionである場合の別のアプローチ:

   var toRemove = workspace.RoleAssignments
                              .FirstOrDefault(spa => spa.Member.Name == shortName)
   if (toRemove != null) workspace.RoleAssignments.Remove(toRemove);

これには、Enumerable拡張メソッドが必要です。 (.NET 2.0で動けない場合は、Monoをコピーできます)。アイテムを取得できないが、インデックスを取得する必要があるカスタムコレクションである場合、Selectなどの他のEnumerableメソッドのいくつかは整数インデックスを渡します。

7
MichaelGG

これはかなり良い方法です

http://support.Microsoft.com/kb/555972

        System.Collections.ArrayList arr = new System.Collections.ArrayList();
        arr.Add("1");
        arr.Add("2");
        arr.Add("3");

        /*This throws an exception
        foreach (string s in arr)
        {
            arr.Remove(s);
        }
        */

        //where as this works correctly
        Console.WriteLine(arr.Count);
        foreach (string s in new System.Collections.ArrayList(arr)) 
        {
            arr.Remove(s);
        }
        Console.WriteLine(arr.Count);
        Console.ReadKey();
2
Jb

これは私の一般的なソリューションです

public static IEnumerable<T> Remove<T>(this IEnumerable<T> items, Func<T, bool> match)
    {
        var list = items.ToList();
        for (int idx = 0; idx < list.Count(); idx++)
        {
            if (match(list[idx]))
            {
                list.RemoveAt(idx);
                idx--; // the list is 1 item shorter
            }
        }
        return list.AsEnumerable();
    }

拡張メソッドが参照渡しをサポートしていれば、もっと簡単に見えるでしょう!使用法:

var result = string[]{"mike", "john", "ALi"}
result = result.Remove(x => x.Username == "mike").ToArray();
Assert.IsTrue(result.Length == 2);

編集:インデックス(idx)をデクリメントしてアイテムを削除する場合でも、リストのループが有効なままであることを確認しました。

2
Jalal El-Shaer

コレクションをループしている間にこれを行い、コレクションの例外を変更しないように、これは私が過去に取ったアプローチです(元のコレクションの最後にある.ToList()に注意してください、これはメモリに別のコレクションを作成します、既存のコレクションを変更できます)

foreach (SPRoleAssignment spAssignment in workspace.RoleAssignments.ToList())
{
    if (spAssignment.Member.Name == shortName)
    {
        workspace.RoleAssignments.Remove(spAssignment);
    }
}
0
Anthony Shaw

それを行う最良の方法は、linqを使用することです。

クラスの例:

 public class Product
    {
        public string Name { get; set; }
        public string Price { get; set; }      
    }

Linqクエリ:

var subCollection = collection1.RemoveAll(w => collection2.Any(q => q.Name == w.Name));

このクエリは、Namecollection1の要素Nameに一致する場合、collection2からすべての要素を削除します

使用することを忘れないでください:using System.Linq;

0
john.kernel

ここで多くの良い反応;私は特にラムダ式が好きです...とてもきれいです。ただし、コレクションのタイプを指定していないことは忘れていました。これは、便利なRemoveAll()ではなく、Remove(int)とRemove(SPPrincipal)のみを持つSPRoleAssignmentCollection(MOSSから)です。それで、より良い提案がない限り、私はこれに落ち着きました。

foreach (SPRoleAssignment spAssignment in workspace.RoleAssignments)
                        {
                            if (spAssignment.Member.Name != shortName) continue;
                            workspace.RoleAssignments.Remove((SPPrincipal)spAssignment.Member);
                            break;
                        }
0
Dan

辞書コレクションの観点と同様に、私はこれを行いました。

Dictionary<string, bool> sourceDict = new Dictionary<string, bool>();
sourceDict.Add("Sai", true);
sourceDict.Add("Sri", false);
sourceDict.Add("SaiSri", true);
sourceDict.Add("SaiSriMahi", true);

var itemsToDelete = sourceDict.Where(DictItem => DictItem.Value == false);

foreach (var item in itemsToDelete)
{
    sourceDict.Remove(item.Key);
}

注:上記のコードは.Netクライアントプロファイル(3.5および4.5)で失敗し、一部のビューアは.Net4.0でも同様に失敗すると述べましたどの設定が問題の原因かわからない。

そのため、Whereステートメントの以下のコード(.ToList())に置き換えて、このエラーを回避します。 「コレクションが変更されました。列挙操作が実行されない可能性があります。」

var itemsToDelete = sourceDict.Where(DictItem => DictItem.Value == false).ToList();

.Net4.5以降のMSDNごとのクライアントプロファイルは廃止されました。 http://msdn.Microsoft.com/en-us/library/cc656912(v = vs.110).aspx

0
Sai

コレクションの使用方法に応じて、別のアプローチを使用できます。割り当てを1回ダウンロードする場合(アプリの実行時など)、コレクションをその場でハッシュテーブルに変換できます。

ショートネーム=> SPRoleAssignment

これを行うと、短い名前でアイテムを削除するときに、キーでハッシュテーブルからアイテムを削除するだけで済みます。

残念ながら、これらのSPRoleAssignmentsを大量にロードする場合、明らかに時間の面でこれ以上のコスト効率は得られません。 Linqの使用について他の人が行った提案は、.NET Frameworkの新しいバージョンを使用している場合には有効ですが、そうでない場合は、使用している方法に固執する必要があります。

0
Ed Altorfer

最初にアイテムを保存してから、削除します。

_var itemsToDelete = Items.Where(x => !!!your condition!!!).ToArray();
for (int i = 0; i < itemsToDelete.Length; ++i)
    Items.Remove(itemsToDelete[i]);
_

ItemクラスでGetHashCode()をオーバーライドする必要があります。

0
Stas BZ