[英]Is List.Find<T> considered dangerous? What's a better way to do List<T>.Find(Predicate<T>)?
我曾經認為List<T> 被認為是危險的。 我的觀點是,我認為 default(T) 不是一個安全的返回值! 許多其他人也這么認為考慮以下幾點:
List<int> evens = new List<int> { 0, 2, 4, 6, , 8};
var evenGreaterThan10 = evens.Find(c=> c > 10);
// evenGreaterThan10 = 0 #WTF
值類型的 default(T) 為 0,因此返回 0 是上面的代碼段!
我不喜歡這樣,所以我添加了一個名為 TryFind 的擴展方法,它返回一個布爾值並接受除 Predicate 之外的輸出參數,類似於著名的 TryParse 方法。
編輯:
這是我的 TryFind 擴展方法:
public static bool TryFind<T>(this List<T> list, Predicate<T> predicate, out T output)
{
int index = list.FindIndex(predicate);
if (index != -1)
{
output = list[index];
return true;
}
output = default(T);
return false;
}
在通用列表上查找的方法是什么?
我不。 我做.Where()
evens.Where(n => n > 10); // returns empty collection
evens.Where(n => n > 10).First(); // throws exception
evens.Where(n => n > 10).FirstOrDefault(); // returns 0
第一種情況只返回一個集合,因此我可以簡單地檢查計數是否大於 0 以了解是否有任何匹配項。
使用第二個時,我包裝在一個專門處理 InvalidOperationException 的 try/catch 塊中,以處理空集合的情況,並重新拋出(冒泡)所有其他異常。 這是我最少使用的一個,僅僅是因為如果我可以避免的話,我不喜歡編寫 try/catch 語句。
在第三種情況下,當不存在匹配項時,我對返回默認值 (0) 的代碼沒問題——畢竟,我確實明確說“給我第一個或默認值”。 因此,如果我遇到問題,我可以閱讀代碼並理解為什么會發生這種情況。
更新:
對於 .NET 2.0 用戶,我不推薦評論中建議的 hack。 它違反了 .NET 的許可協議,任何人都不會支持它。
相反,我看到了兩種方法:
升級。 大多數在 2.0 上運行的東西將在 3.5 上運行(或多或少)不變。 而且有這么多在3.5(不只是LINQ)這確實是值得升級的有它可用的努力。 由於 4.0 有一個新的 CLR 運行時版本,因此 2.0 和 4.0 之間的重大更改比 2.0 和 3.5 之間的要多,但如果可能的話,我建議一直升級到 4.0。 沒有很好的理由坐在一個框架版本中編寫新代碼,該版本已經有 3 個主要版本(是的,我將 3.0、3.5 和 4.0 都視為主要版本......),因為你正在使用那個版本。
查找Find
問題的解決方法。 我建議要么像你一樣使用FindIndex
,因為在沒有找到任何東西時返回的 -1 永遠不會有歧義,或者像你一樣用FindIndex
實現一些東西。 我不喜歡out
語法,但在我編寫一個不使用它的實現之前,我需要一些關於在沒有找到任何東西時想要返回的內容的輸入。
在那之前, TryFind
可以被認為是好的,因為它與 .NET 中以前的功能保持一致,例如Integer.TryParse
。 而且你確實有一種體面的方式來處理沒有發現的事情
List<Something> stuff = GetListOfStuff(); Something thing; if (stuff.TryFind(t => t.IsCool, thing)) { // do stuff that's good. thing is the stuff you're looking for. } else { // let the user know that the world sucks. }
您可以使用FindIndex
而不是Find
。 它返回找到的元素的索引,如果沒有找到元素,則返回-1
。
http://msdn.microsoft.com/en-us/library/x1xzf2ca%28v=VS.80%29.aspx
如果您知道列表中沒有default(T)
值或者default(T)
返回值不能作為結果,那也沒關系。
您可以輕松實現自己的。
public static T Find<T>(this List<T> list, Predicate<T> match, out bool found)
{
found = false;
for (int i = 0; i < list.Count; i++)
{
if (match(list[i]))
{
found = true;
return list[i];
}
}
return default(T);
}
並在代碼中:
bool found;
a.Find(x => x > 5, out found);
其他選項:
evens.First(predicate);//throws exception
evens.FindAll(predicate);//returns a list of results => use .Count.
這取決於您可以使用的框架版本。
我在 Reddit 上給出的答案相同。
Enumerable.FirstOrDefault( predicate )
如果 T 的默認值有問題,可以使用 Nullable 來獲取更有意義的默認值:
List<int?> evens = new List<int?> { 0, 2, 4, 6, 8 };
var greaterThan10 = evens.Find(c => c > 10);
if (greaterThan10 != null)
{
// ...
}
這也不需要首先額外調用 Exists()。
首先調用Exists()
將有助於解決問題:
int? zz = null;
if (evens.Exists(c => c > 10))
zz = evens.Find(c => c > 10);
if (zz.HasValue)
{
// .... etc ....
}
稍微啰嗦一點,但可以完成工作。
只是為了好玩
evens.Where(c => c > 10)
.Select(c => (int?)c)
.DefaultIfEmpty(null)
.First();
受上面的Jaroslav Jandek 的啟發,我將創建一個擴展,如果匹配則返回 bool true 並在匹配時將對象傳入:
static class Extension
{
public static bool TryFind<T>(this List<T> list, Predicate<T> match, out T founditem)
{
for (int i = 0; i < list.Count; i++)
{
if (match(list[i]))
{
founditem = list[i];
return true;
}
}
founditem = default(T);
return false;
}
}
然后它可以在 List 對象 'a' 上使用,並使用 'out var' 語法像這樣調用:
if (a.TryFind(x => x > 5, out var founditem)){
//enter code here to use 'founditem'
};
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.