Dictionary.FirstOrDefault() how to determine if a result was found
C#.NetLinqC# Problem Overview
I have (or wanted to have) some code like this:
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>'
}
I also tried changing the offending line like this:
if ( entry != default(KeyValuePair<string,int>) )
But that also gives a compile error:
Operator '!=' cannot be applied to operands of type 'System.Collections.Generic.KeyValuePair<string,int>' and 'System.Collections.Generic.KeyValuePair<string,int>'
What gives here?
C# Solutions
Solution 1 - C#
Jon's answer will work with Dictionary<string, int>
, as that can't have a null key value in the dictionary. It wouldn't work with Dictionary<int, string>
, however, as that doesn't represent a null key value... the "failure" mode would end up with a key of 0.
Two options:
Write a TryFirstOrDefault
method, like this:
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;
}
}
Alternatively, project to a nullable type:
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>
}
Solution 2 - C#
Do it this way:
if ( entry.Key != null )
The thing is that the FirstOrDefault
method returns a KeyValuePair<string, int>
which is a value type, so it cannot ever be null
. You have to determine if a value was found by checking if at least one of its Key
, Value
properties has its default value. Key
is of type string
, so checking that for null
makes sense considering that the dictionary could not have an item with a null
key.
Other approaches you could use:
var entry = dict.Where(e => e.Value == 1)
.Select(p => (int?)p.Value)
.FirstOrDefault();
This projects the results into a collection of nullable ints, and if that is empty (no results) you get a null -- there's no way you can mistake that for the int
that a successful search would yield.
Solution 3 - C#
Regardless of the types of Key and Value, you could do something like this:
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();
}
Solution 4 - C#
The clearest code I think is this:
if (dict.ContainsValue(value))
string key = dict.First(item => item.Value == value).Key;
else
// do somehing else
Though from the standpoint of speed it's not nice, but there is no better solution. This means that the dictionary will be searched with a slow search a second time. The Dictionary class should be improved by offering a method 'bool TryGetKey(value)'. It looks a little bit strange - because a dictionary is thought to be used in the other direction - but sometimes it's unavoidable to translate backwards.
Solution 5 - C#
for nullable value types, just check.
if ( entry.Value != null ) {
//do stuff
}
for non-nullable types check againts default value, for int, 0.
if ( entry.Value != 0) {
//do stuff
}
Solution 6 - C#
To be fair, casting the object or using a select statement is unnecessary, I wouldn't rely on a try catch to fix the issue either.
Since you're using Linq anyway, what's wrong with using .Any?
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;
}
Obviously, play with it to fit your solution, but it's a fairly quick check that stops if it finds an item in the collection with that value. So it won't check all values if a match has been found.
MSDN Documentation: https://msdn.microsoft.com/en-us/library/bb534972(v=vs.110).aspx
Solution 7 - C#
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;
}
Solution 8 - C#
Linq FirstOrDefault applying to a Dictionary returns not nullable keyValuePair object anyway. The right way is to check result key or value that it doesn't equal default type value