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

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つの追加変数を使用することなく、プロパティ(この場合、名前)で削除する項目を見つけることです。

質問へのコメント (4)

RoleAssignmentsがListの場合、以下のコードを使用することができます。

workSpace.RoleAssignments.RemoveAll(x =>x.Member.Name == shortName);
解説 (5)
ソリューション

もし、コレクションのメンバーのいずれかのプロパティでアクセスしたい場合は、代わりに Dictionary または KeyedCollection を使うことを検討するとよいでしょう。こうすれば、探しているアイテムを検索する必要がなくなります'。

そうでなければ、せめてこれくらいはしてほしい。

foreach (SPRoleAssignment spAssignment in workspace.RoleAssignments)
{
    if (spAssignment.Member.Name == shortName)
    {
        workspace.RoleAssignments.Remove(spAssignment);
        break;
    }
}
解説 (3)

@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で「ボブ」を削除し、残りのすべての要素を残します。 次回はループidx = 1ですが。 list [1]は、「Mary」ではなく「Ted」になりました。 誤って「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);
}
解説 (7)

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

例えば。

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

理由は次のとおりです。

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

これをpre c#3.0時代に実装するのに行き詰まっている場合。 2つのオプションがあります。

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

例えば。

List list2 = new List() ; 
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)はこの目的に適しています。

解説 (0)

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

    public static void RemoveAll(this ICollection source, 
                                    Func 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/

解説 (1)

コレクションはどのようなタイプですか?Listであれば、便利な"RemoveAll"を使用することができます。

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

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

Listではなく、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メソッドが、あなたのために整数インデックスを渡してくれます。

解説 (0)

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

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();
解説 (0)

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

public static IEnumerable Remove(this IEnumerable items, Func 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)を減額してアイテムを削除した場合でも、リストループが有効であることを確認しました。

解説 (4)

これを行う最良の方法は、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));

このクエリは、「名前」が「collection2」の要素「名前」と一致する場合、「collection1」からすべての要素を削除します。

必ず使用してください: System.Li nq;を使用します。

解説 (0)

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

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

コレクションの使用方法に応じて、別のアプローチをとることができます。 課題を1回ダウンロードする場合(例:.、アプリが実行されると)、その場でコレクションをハッシュテーブルに変換できます。

ショートネーム=> SPROleAssignment。

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

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

解説 (0)

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

Dictionary sourceDict = new Dictionary();
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 Client Profile(3.5および4.5)で失敗します。また、一部の視聴者はそうであると述べました。 .Net4.0で失敗した場合も、どの設定が問題を引き起こしているのかわかりません。

そのため、そのエラーを回避するために、Whereステートメントの下のコード(.ToList())に置き換えます。 「収集が変更されました。列挙操作を実行できない場合があります。」。

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

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

解説 (0)

アイテムを削除するよりも、最初にアイテムを保存します。

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

アイテムクラスの「GetHashCode()」をオーバーライドする必要があります。

解説 (0)

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

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