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つの追加変数を使用せずに、プロパティ(この場合は名前)で削除するアイテムを見つけることです。
プロパティの1つを使用してコレクションのメンバーにアクセスする場合は、Dictionary<T>
または KeyedCollection<T>
代わりに。この方法では、探しているアイテムを検索する必要はありません。
そうでなければ、少なくともこれを行うことができます:
foreach (SPRoleAssignment spAssignment in workspace.RoleAssignments)
{
if (spAssignment.Member.Name == shortName)
{
workspace.RoleAssignments.Remove(spAssignment);
break;
}
}
RoleAssignmentsがList<T>
の場合、次のコードを使用できます。
workSpace.RoleAssignments.RemoveAll(x =>x.Member.Name == shortName);
@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);
}
単純なリスト構造の場合、最も効率的な方法は、Predicate RemoveAll実装を使用することです。
例えば。
workSpace.RoleAssignments.RemoveAll(x =>x.Member.Name == shortName);
その理由は次のとおりです。
以前の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)は、この目的により適しています。
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/
コレクションはどのタイプですか?リストの場合、役立つ「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メソッドのいくつかは整数インデックスを渡します。
これはかなり良い方法です
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();
これは私の一般的なソリューションです
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)をデクリメントしてアイテムを削除する場合でも、リストのループが有効なままであることを確認しました。
コレクションをループしている間にこれを行い、コレクションの例外を変更しないように、これは私が過去に取ったアプローチです(元のコレクションの最後にある.ToList()に注意してください、これはメモリに別のコレクションを作成します、既存のコレクションを変更できます)
foreach (SPRoleAssignment spAssignment in workspace.RoleAssignments.ToList())
{
if (spAssignment.Member.Name == shortName)
{
workspace.RoleAssignments.Remove(spAssignment);
}
}
それを行う最良の方法は、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));
このクエリは、Name
がcollection1
の要素Name
に一致する場合、collection2
からすべての要素を削除します
使用することを忘れないでください:using System.Linq;
ここで多くの良い反応;私は特にラムダ式が好きです...とてもきれいです。ただし、コレクションのタイプを指定していないことは忘れていました。これは、便利な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;
}
辞書コレクションの観点と同様に、私はこれを行いました。
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
コレクションの使用方法に応じて、別のアプローチを使用できます。割り当てを1回ダウンロードする場合(アプリの実行時など)、コレクションをその場でハッシュテーブルに変換できます。
ショートネーム=> SPRoleAssignment
これを行うと、短い名前でアイテムを削除するときに、キーでハッシュテーブルからアイテムを削除するだけで済みます。
残念ながら、これらのSPRoleAssignmentsを大量にロードする場合、明らかに時間の面でこれ以上のコスト効率は得られません。 Linqの使用について他の人が行った提案は、.NET Frameworkの新しいバージョンを使用している場合には有効ですが、そうでない場合は、使用している方法に固執する必要があります。
最初にアイテムを保存してから、削除します。
_var itemsToDelete = Items.Where(x => !!!your condition!!!).ToArray();
for (int i = 0; i < itemsToDelete.Length; ++i)
Items.Remove(itemsToDelete[i]);
_
ItemクラスでGetHashCode()
をオーバーライドする必要があります。