83

Given:

class Program
{
    private static readonly List<(int a, int b, int c)> Map = new List<(int a, int b, int c)>()
    {
        (1, 1, 2),
        (1, 2, 3),
        (2, 2, 4)
    };

    static void Main(string[] args)
    {
        var result = Map.FirstOrDefault(w => w.a == 4 && w.b == 4);

        if (result == null)
            Console.WriteLine("Not found");
        else
            Console.WriteLine("Found");
    }
}

In the above example, a compiler error is encountered at line if (result == null).

CS0019 Operator '==' cannot be applied to operands of type '(int a, int b, int c)' and '<null>'

How would I go about checking that the tuple is found prior to proceeding in my "found" logic?

Prior to using the new c# 7 tuples, I would have this:

class Program
{
    private static readonly List<Tuple<int, int, int>> Map = new List<Tuple<int, int, int>>()
    {
        new Tuple<int, int, int> (1, 1, 2),
        new Tuple<int, int, int> (1, 2, 3),
        new Tuple<int, int, int> (2, 2, 4)
    };

    static void Main(string[] args)
    {
        var result = Map.FirstOrDefault(w => w.Item1 == 4 && w.Item2 == 4);

        if (result == null)
            Console.WriteLine("Not found");
        else
            Console.WriteLine("Found");
    }
}

Which worked fine. I like the more easily interpreted intention of the new syntax, but am unsure on how to null check it prior to acting on what was found (or not).

11
  • 10
    Value tuples are values types. They can't be null Commented Jun 1, 2017 at 12:35
  • 4
    In this case the default will be a tuple of 3 zeros instead of null. So you could check for that instead.
    – juharr
    Commented Jun 1, 2017 at 12:37
  • 5
    @juharr but this will break if list contains (0,0,0) item, so not quite reliable.
    – Evk
    Commented Jun 1, 2017 at 12:38
  • @Evk (0,0,0) doesn't match the predicate in this case, but it could be an issue in other cases.
    – juharr
    Commented Jun 1, 2017 at 12:39
  • 1
    @PanagiotisKanavos Not true. What if the colleciton contains (0,0,0) and the predicate matches it? Then you have to either do an up front check with Any or xanatos's trick with Take(1).ToArray() to differentiate between not found and found, but just happens to match the default.
    – juharr
    Commented Jun 1, 2017 at 12:54

10 Answers 10

88

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) {...}
8
  • 1
    But the LINQ query posted would clearly not find a tuple in the list that has an a and b of 4, so how would I go about verifying that?
    – Kritner
    Commented Jun 1, 2017 at 12:37
  • 1
    You'll get a default value back - a tuple with all fields set to defaults ie 0. Commented Jun 1, 2017 at 12:38
  • which could be a valid case in the Map, I'm not actually using several ints it was only meant as an example.
    – Kritner
    Commented Jun 1, 2017 at 12:39
  • 9
    In C# 7.1 you can now just write: if (result.Equals(default)) and it will infer the type for default automatically. To use C# 7.1 you need a recent version of Visual Studio 2017 and also set the C# version to 7.1 in your project build settings (Advanced...).
    – evilkos
    Commented Dec 7, 2017 at 12:09
  • 2
    Optionis perfectly suitable to be a struct, similar to Nullable. Otherwise you need to deal with nullability of an optional which is confusing. Commented Sep 2, 2019 at 23:57
20

Just to add one more alternative to deal with value types and FirstOrDefault: use Where and cast the result to nullable type:

var result = Map.Where(w => w.a == 4 && w.b == 4)
   .Cast<(int a, int b, int c)?>().FirstOrDefault();

if (result == null)
   Console.WriteLine("Not found");
else
   Console.WriteLine("Found");

You can even make an extension method of it:

public static class Extensions {
    public static T? StructFirstOrDefault<T>(this IEnumerable<T> items, Func<T, bool> predicate) where T : struct {
        return items.Where(predicate).Cast<T?>().FirstOrDefault();
    }
}

Then your original code will compile (assuming you replace FirstOrDefault with StructFirstOrDefault).

3
  • 1
    You can't probably name it FirstOrDefault(): overload resolution isn't done based on generic restrictions.
    – xanatos
    Commented Jun 2, 2017 at 23:04
  • @xanatos you are right - it's not the best name, changed that (though it still compiles and works fine with OP example code).
    – Evk
    Commented Jun 3, 2017 at 15:38
  • 2
    Or maybe just call it FirstOrNull, since its purpose is to return a null value when nothing is found. +1 Commented Oct 24, 2018 at 10:52
20

In C# 7.3, it's very clean:

var result = Map.FirstOrDefault(w => w.a == 4 && w.b == 4);
if (result == default) {
    Console.WriteLine("Not found");
} else {
    Console.WriteLine("Found");
}
0
7

As written by Panagiotis you can't do it directly... You could "cheat" a little:

var result = Map.Where(w => w.a == 4 && w.b == 4).Take(1).ToArray();

if (result.Length == 0)
    Console.WriteLine("Not found");
else
    Console.WriteLine("Found");

You take up to one element with the Where and put the result in an array of length 0-1.

Alternatively you could repeat the comparison:

var result = Map.FirstOrDefault(w => w.a == 4 && w.b == 4);

if (result.a == 4 && result.b == 4)
    Console.WriteLine("Not found");

This second option won't work if you were looking for

var result = Map.FirstOrDefault(w => w.a == 0 && w.b == 0);

In this case the "default" value returned by FirstOrDefault() has a == 0 and b == 0.

Or you could simply create a "special" FirstOrDefault() that has a out bool success (like the various TryParse):

static class EnumerableEx
{
    public static T FirstOrDefault<T>(this IEnumerable<T> source, Func<T, bool> predicate, out bool success)
    {
        if (source == null)
        {
            throw new ArgumentNullException(nameof(source));
        }

        if (predicate == null)
        {
            throw new ArgumentNullException(nameof(predicate));
        }

        foreach (T ele in source)
        {
            if (predicate(ele))
            {
                success = true;
                return ele;
            }
        }

        success = false;
        return default(T);
    }
}

use it like:

bool success;
var result = Map.FirstOrDefault(w => w.a == 4 && w.b == 4, out success);

Other possible extension method, ToNullable<>()

static class EnumerableEx
{
    public static IEnumerable<T?> ToNullable<T>(this IEnumerable<T> source) where T : struct
    {
        return source.Cast<T?>();
    }
}

Use it like:

var result = Map.Where(w => w.a == 4 && w.b == 4).ToNullable().FirstOrDefault();

if (result == null)

Note that result is a T?, so you'll need to do result.Value to use its value.

7

If you are sure your data set won't include (0, 0, 0), then as others have said, you can check for the default:

if (result.Equals(default(ValueTuple<int,int,int>))) ...

If that value may occur though, then you could use First and catch the exception when there's no match:

class Program
{
    private static readonly List<(int a, int b, int c)> Map = 
        new List<(int a, int b, int c)>()
    {
        (1, 1, 2),
        (1, 2, 3),
        (2, 2, 4),
        (0, 0, 0)
    };

    static void Main(string[] args)
    {
        try
        {
            Map.First(w => w.a == 0 && w.b == 0);
            Console.WriteLine("Found");
        }
        catch (InvalidOperationException)
        {
            Console.WriteLine("Not found");
        }
    }
}

Alternatively, you could use a library, such as my own Succinc<T> library that provide a TryFirst method that returns a "maybe" type of none if no match, or the item if matched:

class Program
{
    private static readonly List<(int a, int b, int c)> Map = 
        new List<(int a, int b, int c)>()
    {
        (1, 1, 2),
        (1, 2, 3),
        (2, 2, 4),
        (0, 0, 0)
    };

    static void Main(string[] args)
    {
        var result = Map.TryFirst(w => w.a == 0 && w.b == 0);
        Console.WriteLine(result.HasValue ? "Found" : "Not found");
    }
}
2
  • You could add Deconstruct to Option, Maybe for a Go-style result, eg var (found,value) = Map.TryFirst(...). Commented Jun 1, 2017 at 14:10
  • @PanagiotisKanavos, Hey that's a really good idea. I'll add that to the feature list for the next release. Thanks.
    – David Arno
    Commented Jun 1, 2017 at 14:52
6

Your check could be the following:

if (!Map.Any(w => w.a == 4 && w.b == 4))
{
    Console.WriteLine("Not found");
}
else
{
    var result = Map.First(w => w.a == 4 && w.b == 4);
    Console.WriteLine("Found");
}
6
  • I don't think you need the result == in that first if.
    – juharr
    Commented Jun 1, 2017 at 12:41
  • your syntax is a bit off, the first if should be if (Map.Any(...)), but that still would not work in the desired way, it is always "Found"
    – Kritner
    Commented Jun 1, 2017 at 12:41
  • if you correct the syntax on this answer it seems like the easiest solution if (!Map.Any(w => w.a == 4 && w.b == 4))
    – Scrobi
    Commented Jun 1, 2017 at 12:49
  • @Kritner It would work if changed to if(!Map.Any(w => w.a == 4 && w.b == 4)) but it does iterate Map twice for all "Found" cases.
    – juharr
    Commented Jun 1, 2017 at 12:50
  • Ah you are right @Scrobi. It would iterate twice, which might be an issue with large datasets. If you have a smaller dataset or performance isn't a huge concern, the improved readability is worth it in my opinion. Commented Jun 1, 2017 at 12:56
6

You need:

if (result.Equals(default)) Console.WriteLine(...

(c# > 7.1)

4

ValueTuple is the underlying type used for the C#7 tuples. They cannot be null as they are value types. You can test them for default though, but that might actually be a valid value.

Also, the equality operator is not defined on ValueTuple, so you must use Equals(...).

static void Main(string[] args)
{
    var result = Map.FirstOrDefault(w => w.Item1 == 4 && w.Item2 == 4);

    if (result.Equals(default(ValueTuple<int, int, int>)))
        Console.WriteLine("Not found");
    else
        Console.WriteLine("Found");
}
1

how i did it with c# 7.3

T findme;
var tuple = list.Select((x, i) => (Item: x, Index: i)).FirstOrDefault(x => x.Item.GetHashCode() == findme.GetHashCode());

if (tuple.Equals(default))
    return;

...
var index = tuple.Index;
0

Most of the answers above imply that your resulting element cannot be default(T), where T is your class/tuple.

A simple way around that is to use an approach as below:

var result = Map
   .Select(t => (t, IsResult:true))
   .FirstOrDefault(w => w.t.Item1 == 4 && w.t.Item2 == 4);

Console.WriteLine(result.IsResult ? "Found" : "Not found");

This sample uses C# 7.1 implied tuple names (and ValueTuple package for C# 7), but you can give the name to your tuple elements explicitly if required, or use a simple Tuple<T1,T2> instead.

Not the answer you're looking for? Browse other questions tagged or ask your own question.