Reputation: 1757
I am trying to find a cleaner way of returning a default value when there is no match found. An example I have written to best demonstrate my question is shown below from LinqPad
So basically if given Age
is not found in the list SingleOrDefault
returns a null
as normal. So instead of returning a null
I pick the highest Threshold
from the regardless of what the Age
value is.
However instead of doing if
or using ?? (null coalescing operator)
is there a cleaner way of achieving this? Perhaps setting a default value inside the get
and set
of Age property inside the test
class?
void Main()
{
var list = new List<test>()
{
new test ( 55, 27 ),
new test ( 56, 28),
new test ( 57, 29),
new test ( 59, 30),
new test ( 60, 31) //60+
};
var res = list.SingleOrDefault(x => x.Age == 61);
if (res == null)
{
list.Max(l => l.Threshold).Dump();
}
else
{
res.Threshold.Dump();
}
}
class test
{
public int Age
{
get;
set;
}
public int Threshold
{
get;
set;
}
public test(int age, int threshold)
{
Age = age;
Threshold = threshold;
}
}
Upvotes: 1
Views: 2154
Reputation: 43384
I guess you would like to have a LINQ method SingleOrMax
, that you could use like this:
var res = list.SingleOrMax(x => x.Age == 61, x => x.Threshold);
The first expression is the predicate for SingleOrDefault
, and the second expression selects the key that will be used for finding the max element, if needed.
Here it is:
public static TSource SingleOrMax<TSource, TMaxKey>(this IEnumerable<TSource> source,
Func<TSource, bool> predicate, Func<TSource, TMaxKey> maxKeySelector)
{
var result = source.SingleOrDefault(predicate);
if (result != default) return result;
var maxKeyComparer = Comparer<TMaxKey>.Default;
TSource max = default;
TMaxKey maxKey = default;
int count = 0;
foreach (var item in source)
{
var key = maxKeySelector(item);
if (count == 0 || maxKeyComparer.Compare(key, maxKey) > 0)
{
max = item;
maxKey = key;
}
count++;
}
// If you remove the line bellow, then rename this method to SingleOrMaxOrDefault
if (count == 0) throw new InvalidOperationException("Sequence contains no elements");
return max;
}
Upvotes: 1
Reputation: 781
You could use DefaultIfEmpty()
of LINQ:
var res = list.Where(x => x.Age == 61)
.Select(t => t)
.DefaultIfEmpty(list.First(x => x.Threshold == list.Max(t => t.Threshold)))
.SingleOrDefault();
Upvotes: 5
Reputation: 3178
You could always go with an extension method, although it seems a bit overkill.
public static Test SingleAgeOrMaxThreshold(this IEnumerable<Test> items, int age)
{
Test max = null;
foreach (Test t in items)
{
if (t.Age == age)
return t;
if (max == null || t.Threshold > max.Threshold)
max = t;
}
return max;
}
Upvotes: 0