Dictionary. FirstOrDefault()如何确定是否找到了结果

我有(或者想要)一些这样的代码:

IDictionary<string,int> dict = new Dictionary<string,int>();
// ... Add some stuff to the dictionary.


// Try to find an entry by value (if multiple, don't care which one).
var entry = dict.FirstOrDefault(e => e.Value == 1);
if ( entry != null ) {
// ^^^ above gives a compile error:
// Operator '!=' cannot be applied to operands of type 'System.Collections.Generic.KeyValuePair<string,int>' and '<null>'
}

我还试着改变了冒犯的台词,比如:

if ( entry != default(KeyValuePair<string,int>) )

但这也会导致编译错误:

Operator '!=' cannot be applied to operands of type 'System.Collections.Generic.KeyValuePair<string,int>' and 'System.Collections.Generic.KeyValuePair<string,int>'

这是怎么回事?

72905 次浏览

这样做:

if ( entry.Key != null )

问题是 FirstOrDefault方法返回的 KeyValuePair<string, int>KeyValuePair<string, int>0,所以它永远不可能是 null。您必须通过检查它的 KeyValue属性中是否至少有一个具有默认值来确定是否找到了值。Key的类型是 string,因此,考虑到字典中不能有 null键的项,检查 null是有意义的。

你可以使用的其他方法:

var entry = dict.Where(e => e.Value == 1)
.Select(p => (int?)p.Value)
.FirstOrDefault();

这将结果投影到一个可空整数集合中,如果这个整数集合是空的(没有结果) ,那么您将得到一个 null ——您不可能将其错认为成功的搜索将产生的 int

Jon 的答案将与 Dictionary<string, int>一起使用,因为它在字典中不能有空键值。但是,它无法与 Dictionary<int, string>一起工作,因为 代表不是一个空键值... ... “失败”模式最终的键值为0。

两个选择:

编写一个 TryFirstOrDefault方法,如下所示:

public static bool TryFirstOrDefault<T>(this IEnumerable<T> source, out T value)
{
value = default(T);
using (var iterator = source.GetEnumerator())
{
if (iterator.MoveNext())
{
value = iterator.Current;
return true;
}
return false;
}
}

或者,项目为可为空的类型:

var entry = dict.Where(e => e.Value == 1)
.Select(e => (KeyValuePair<string,int>?) e)
.FirstOrDefault();


if (entry != null)
{
// Use entry.Value, which is the KeyValuePair<string,int>
}

对于可为空的值类型,只需检查。

if ( entry.Value != null ) {
//do stuff
}

对于非空类型,检查默认值,对于 int,0。

if ( entry.Value != 0) {
//do stuff
}

不管键和值的类型如何,您都可以执行以下操作:

    static void Main(string[] args)
{
var dict = new Dictionary<int, string>
{
{3, "ABC"},
{7, "HHDHHGKD"}
};


bool found = false;
var entry = dict.FirstOrDefault(d => d.Key == 3 && (found=true));
if (found)
{
Console.WriteLine("found: " + entry.Value);
}
else
{
Console.WriteLine("not found");
}
Console.ReadLine();
}

公平地说,强制转换对象或使用 select 语句是不必要的,我也不会依赖 try catch 来修复这个问题。

反正你也在用 Linq 用有什么不好的,有吗?

var entry;
if (dict.Any(e => e.Value == 1))
{
// Entry was found, continue work...
entry = dict.FirstOrDefault(e => e.Value == 1);
}
else
{
// Entry was not found.
entry = -1;
}

显然,可以使用它来适应您的解决方案,但是如果在集合中找到一个具有该值的项目,那么这是一个相当快速的检查。因此,如果找到匹配,它不会检查所有值。

MSDN 文档: Https://msdn.microsoft.com/en-us/library/bb534972(v=vs.110).aspx

public static TValue FirstOrDefault<TKey, TValue>(this Dictionary<TKey, TValue> dictionary, Func<KeyValuePair<TKey, TValue>, bool> where)
{
foreach (var kv in dictionary)
{
if (where(kv))
return kv.Value;
}
return default;
}

我认为最清晰的代码是这样的:

if (dict.ContainsValue(value))
string key = dict.First(item => item.Value == value).Key;
else
// do somehing else

虽然从速度的角度来看,这并不好,但没有更好的解决方案。这意味着第二次慢速搜索将搜索字典。Dictionary 类应该通过提供一个方法‘ bool TryGetKey (value)’来改进。它看起来有点奇怪——因为字典被认为是用在另一个方向上的——但是有时候不可避免地要向后翻译。

应用于 Dictionary 的 LinqFirstOrDefault 无论如何都会返回不可为空的 keyValuePair 对象。 正确的方法是检查结果键或值是否等于默认类型值