A common pattern I like to use in C# is one where instead of first checking whether something exists in a collection with Any
then finding it again with First
, I simply call FirstOrDefault
, which both tells me whether it exists and if so, gives me a reference to it:
List<Person> people = ...;
Person found;
if ((found = people.FirstOrDefault(x => x.Age > 10)) != null) {
// Found person with age over 10, and we have them in 'found'
//...
}
This works when the thing being found is a reference type, and can be null. However, I was trying to do the same thing with a Dictionary
's entries:
Dictionary<(int X, int Y), ITileData<(int X, int Y)>> srchField = new();
KeyValuePair<(int X, int Y), ITileData<(int X, int Y)>> next;
while ((next = srchField.FirstOrDefault(x => !x.Value.Reached)) != null) {
// Found next, and we have it in 'next'
//...
}
This, however, doesn't work because srchField.FirstOrDefault(...)
returns a KeyValuePair<TKey, TValue>
, which is a struct. I can work around this by first calling Any
on srchField
, and then calling First
with the same predicate if Any
finds anything, but this is having to do the same search twice. Is there any way I can do the 2-in-1 check existence and store here, only carrying out one search of the dictionary in LINQ?
CodePudding user response:
If you want to ignore the key in your search and your values are reference types, you can just use Values
properties of a dictionary:
srchField.Values.FirstOrDefault(!v => v.Reached)
If you want to do something with keys, it would be a bit more clunky:
srchField.Where(kvp => Predicate(kvp.Key)).Select(kvp => kvp.Value).FirstOrDefault()
CodePudding user response:
Thanks to @TheodorZoulias pointing me in the right direction, and inspired by a combination of the answer from @Samuel and the nice code solution from @JonSkeet (I hadn't actually realized that if LINQ .Take(1)
only finds 0 elements, it actually gives you an enumerable of 0 instead of throwing an exception), I came up with an extension method that works well for doing this:
public static class IEnumerableExtensions {
public static bool TryGetFirst<TSource>(this IEnumerable<TSource> source, Func<TSource, bool> predicate, out TSource first) {
if (predicate == null) {
throw new ArgumentNullException(nameof(predicate));
}
var query = source.Where(predicate).Take(1).ToList();
if (query.Count == 0) {
first = default;
return false;
}
first = query[0];
return true;
}
}
Example usage:
while (srchField.TryGetFirst(x => !x.Value.Reached, out KeyValuePair<(int X, int Y), ITileData<(int X, int Y)>> next)) {
var key = next.Key;
var val = next.Value;
// ... use eg. key.X, key.Y, and val ...
}