How to null check c# 7 tuple in LINQ query?

Value tuples are value types. They can’t be null, which is why the compiler complains. The old Tuple type was a reference type

The result of FirstOrDefault() in this case will be a default instance of an ValueTuple<int,int,int> – all fields will be set to their default value, 0.

If you want to check for a default, you can compare the result with the default value of ValueTuple<int,int,int>, eg:

var result=(new List<(int a, int b, int c)>()
            {
                (1, 1, 2),
                (1, 2, 3),
                (2, 2, 4)
            }
        ).FirstOrDefault(w => w.a == 4 && w.b == 4);

if (result.Equals(default(ValueTuple<int,int,int>)))
{
    Console.WriteLine("Missing!"); 
}

WORD OF WARNING

The method is called FirstOrDefault, not TryFirst. It’s not meant to check whether a value exists or not, although we all (ab)use it this way.

Creating such an extension method in C# isn’t that difficult. The classic option is to use an out parameter:

public static bool TryFirst<T>(this IEnumerable<T> seq,Func<T,bool> filter, out T result) 
{
    result=default(T);
    foreach(var item in seq)
    {
        if (filter(item)) {
            result=item;
            return true;
         }
    }
    return false;
}

Calling this can be simplified in C# 7 as :

if (myList.TryFirst(w => w.a == 4 && w.b == 1,out var result))
{
    Console.WriteLine(result);
}

F# developers can brag that they have a Seq.tryPick that will return None if no match is found.

C# doesn’t have Option types or the Maybe type (yet), but maybe (pun intended) we can build our own:

class Option<T> 
{
    public T Value {get;private set;}

    public bool HasValue {get;private set;}

    public Option(T value) { Value=value; HasValue=true;}    

    public static readonly Option<T> Empty=new Option<T>();

    private Option(){}

    public void Deconstruct(out bool hasValue,out T value)
    {
        hasValue=HasValue;
        value=Value;
    }
}

public static Option<T> TryPick<T>(this IEnumerable<T> seq,Func<T,bool> filter) 
{
    foreach(var item in seq)
    {
        if (filter(item)) {
            return new Option<T>(item);
         }
    }
    return Option<T>.Empty;
}

Which allows writing the following Go-style call:

var (found,value) =myList.TryPick(w => w.a == 4 && w.b == 1);

In addition to the more traditional :

var result=myList.TryPick(w => w.a == 4 && w.b == 1);
if (result.HasValue) {...}

Leave a Comment

Hata!: SQLSTATE[HY000] [1045] Access denied for user 'divattrend_liink'@'localhost' (using password: YES)