KJSR
KJSR

Reputation: 1757

Returning Default Value from List<> when match is not found

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

Answers (3)

Theodor Zoulias
Theodor Zoulias

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

Peter
Peter

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

Parrish Husband
Parrish Husband

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

Related Questions