[英]Best way to remove items from a collection
一旦項目已知,但不是索引,從 C# 中的集合中刪除項目的最佳方法是什么? 這是一種方法,但充其量似乎不優雅。
//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 個附加變量。
如果 RoleAssignments 是List<T>
您可以使用以下代碼。
workSpace.RoleAssignments.RemoveAll(x =>x.Member.Name == shortName);
如果您想通過集合的屬性之一訪問集合的成員,您可以考慮改用Dictionary<T>
或KeyedCollection<T>
。 這樣您就不必搜索您要查找的項目。
否則,你至少可以這樣做:
foreach (SPRoleAssignment spAssignment in workspace.RoleAssignments)
{
if (spAssignment.Member.Name == shortName)
{
workspace.RoleAssignments.Remove(spAssignment);
break;
}
}
@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);
}
如果它是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/
對於簡單的 List 結構,最有效的方法似乎是使用 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) 更適合此目的。
集合是什么類型? 如果是列表,您可以使用有用的“RemoveAll”:
int cnt = workspace.RoleAssignments
.RemoveAll(spa => spa.Member.Name == shortName)
(這適用於 .NET 2.0。當然,如果您沒有較新的編譯器,則必須使用“delegate (SPRoleAssignment spa) { return spa.Member.Name == shortName; }” 而不是 nice lambda 語法。)
如果它不是列表,但仍然是 ICollection,則另一種方法:
var toRemove = workspace.RoleAssignments
.FirstOrDefault(spa => spa.Member.Name == shortName)
if (toRemove != null) workspace.RoleAssignments.Remove(toRemove);
這需要 Enumerable 擴展方法。 (如果您堅持使用 .NET 2.0,您可以將 Mono 復制進來)。 如果是一些不能帶項目但必須帶索引的自定義集合,則其他一些 Enumerable 方法,例如 Select,會為您傳入整數索引。
這是一個很好的方法來做到這一點
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);
}
}
根據您使用收藏的方式,您還可以采用另一種方法。 如果您一次性下載作業(例如,當應用程序運行時),您可以即時將集合轉換為哈希表,其中:
簡稱 => SPRoleAssignment
如果你這樣做,那么當你想通過短名稱刪除一個項目時,你需要做的就是通過鍵從哈希表中刪除該項目。
不幸的是,如果您大量加載這些 SPRoleAssignments,那么就時間而言,這顯然不會更具成本效益。 如果您使用的是 .NET Framework 的新版本,其他人就使用 Linq 提出的建議會很好,但否則,您就必須堅持使用的方法。
這里有很多很好的回應; 我特別喜歡 lambda 表達式……非常干凈。 然而,我疏忽了沒有指定 Collection 的類型。 這是一個 SPRoleAssignmentCollection(來自 MOSS),它只有 Remove(int) 和 Remove(SPPrincipal),而不是方便的 RemoveAll()。 所以,我已經解決了這個問題,除非有更好的建議。
foreach (SPRoleAssignment spAssignment in workspace.RoleAssignments)
{
if (spAssignment.Member.Name != shortName) continue;
workspace.RoleAssignments.Remove((SPPrincipal)spAssignment.Member);
break;
}
類似於 Dictionary Collection 的觀點,我已經這樣做了。
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 Client Profile(3.5 和 4.5)中會失敗,還有一些觀眾提到它在 .Net4.0 中失敗,也不確定是哪些設置導致了問題。
所以用下面的代碼 (.ToList()) 替換 Where 語句,以避免該錯誤。 “集合已修改; 枚舉操作可能無法執行。”
var itemsToDelete = sourceDict.Where(DictItem => DictItem.Value == false).ToList();
根據 MSDN 從 .Net4.5 開始,Client Profile 已停止使用。 http://msdn.microsoft.com/en-us/library/cc656912(v=vs.110).aspx
首先保存您的項目,而不是刪除它們。
var itemsToDelete = Items.Where(x => !!!your condition!!!).ToArray();
for (int i = 0; i < itemsToDelete.Length; ++i)
Items.Remove(itemsToDelete[i]);
您需要在 Item 類中覆蓋GetHashCode()
。
最好的方法是使用 linq。
示例類:
public class Product
{
public string Name { get; set; }
public string Price { get; set; }
}
林克查詢:
var subCollection = collection1.RemoveAll(w => collection2.Any(q => q.Name == w.Name));
該查詢將刪除所有元素collection1
如果Name
匹配任何元素Name
從collection2
記得使用: using System.Linq;
如果你有一個List<T>
,那么List<T>.RemoveAll
是你最好的選擇。 沒有比這更有效的了。 在內部,它使陣列一次移動,更不用說它是 O(N)。
如果你得到的只是一個IList<T>
或一個ICollection<T>
你大概有這三個選項:
public static void RemoveAll<T>(this IList<T> ilist, Predicate<T> predicate) // O(N^2)
{
for (var index = ilist.Count - 1; index >= 0; index--)
{
var item = ilist[index];
if (predicate(item))
{
ilist.RemoveAt(index);
}
}
}
或
public static void RemoveAll<T>(this ICollection<T> icollection, Predicate<T> predicate) // O(N)
{
var nonMatchingItems = new List<T>();
// Move all the items that do not match to another collection.
foreach (var item in icollection)
{
if (!predicate(item))
{
nonMatchingItems.Add(item);
}
}
// Clear the collection and then copy back the non-matched items.
icollection.Clear();
foreach (var item in nonMatchingItems)
{
icollection.Add(item);
}
}
或
public static void RemoveAll<T>(this ICollection<T> icollection, Func<T, bool> predicate) // O(N^2)
{
foreach (var item in icollection.Where(predicate).ToList())
{
icollection.Remove(item);
}
}
選擇 1 或 2。
如果要執行的刪除操作較少(即謂詞在大多數情況下為假),則 1 占用的內存更小且速度更快。
如果要執行更多刪除,則 2 會更快。
3 是最干凈的代碼,但 IMO 的性能很差。 同樣,這一切都取決於輸入數據。
有關一些基准測試的詳細信息,請參閱https://github.com/dotnet/BenchmarkDotNet/issues/1505
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.