74

アイテムが既知であるがインデックスではない場合、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

15 に答える 15

138

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

workSpace.RoleAssignments.RemoveAll(x =>x.Member.Name == shortName);
于 2008-10-16T01:04:31.347 に答える
28

プロパティの 1 つによってコレクションのメンバーにアクセスする場合は、代わりにDictionary<T>orを使用することを検討してください。KeyedCollection<T>これにより、探しているアイテムを検索する必要がなくなります。

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

foreach (SPRoleAssignment spAssignment in workspace.RoleAssignments)
{
    if (spAssignment.Member.Name == shortName)
    {
        workspace.RoleAssignments.Remove(spAssignment);
        break;
    }
}
于 2008-10-16T00:50:48.597 に答える
23

@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]TedMary. 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);
}
于 2008-10-16T04:33:47.430 に答える
10

単純なリスト構造の場合、最も効率的な方法は 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) のような別の構造がこの目的により適しています。

于 2008-10-16T01:12:02.780 に答える
7

コレクションの種類は?リストの場合は、便利な「RemoveAll」を使用できます。

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

(これは .NET 2.0 で動作します。もちろん、新しいコンパイラを使用していない場合は、nice の代わりに "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 メソッドの一部が整数インデックスを渡します。

于 2008-10-16T01:05:28.583 に答える
2

ここにそれを行うためのかなり良い方法があります

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();
于 2009-09-13T01:04:03.757 に答える
2

これは私の一般的な解決策です

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)をデクリメントしてアイテムを削除しても、リストのループが有効なままであることを確認しました。

于 2010-02-17T20:00:27.310 に答える
0

ここにはたくさんの良い反応があります。私は特にラムダ式が好きです...とてもきれいです。しかし、コレクションの種類を指定しなかったのは残念でした。これは、便利な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;
}
于 2008-10-16T14:14:23.297 に答える
0

Save your items first, than delete them.

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

You need to override GetHashCode() in your Item class.

于 2016-01-25T10:05:32.883 に答える
0

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

shortname => SPRoleAssignment

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

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

于 2008-10-16T05:26:38.227 に答える
0

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

foreach (SPRoleAssignment spAssignment in workspace.RoleAssignments.ToList())
{
    if (spAssignment.Member.Name == shortName)
    {
        workspace.RoleAssignments.Remove(spAssignment);
    }
}
于 2012-05-07T19:44:17.717 に答える
0

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

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()) に置き換えます。「コレクションが変更されました。列挙操作が実行されない可能性があります。」</p>

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

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

于 2014-09-04T17:26:11.207 に答える