73 votes

Comment faire pour vérifier le tuple c # 7 dans les requêtes LINQ?

Donnée:

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");
    }
}

Dans l'exemple ci-dessus, un compilateur erreur s'est produite à la ligne if (result == null).

CS0019 Opérateur '==' ne peut pas être appliquée à des opérandes de type '(int a, int b, int c) " et "

Comment puis-je vérifier que le n-uplet est trouvé avant de procéder à mon "trouvé" la logique?

Avant d'utiliser le c# 7 tuples, je voudrais avoir ceci:

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");
    }
}

Qui a bien fonctionné. J'aime le plus facilement interprété l'intention de la nouvelle syntaxe, mais je suis incertain sur la façon de nulle vérifier avant d'agir sur ce qui a été trouvé (ou pas).

83voto

Panagiotis Kanavos Points 14085

La valeur des n-uplets sont les types de valeur. Ils ne peuvent pas être nulle, ce qui est pourquoi le compilateur se plaint. L'ancien Tuple de type est un type de référence

Le résultat de l' FirstOrDefault() dans ce cas sera une instance par défaut de l' ValueTuple<int,int,int> - tous les champs seront mis à leur valeur par défaut, 0.

Si vous souhaitez vérifier un par défaut, vous pouvez comparer le résultat avec la valeur par défaut de ValueTuple<int,int,int>, par exemple:

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!"); 
}

MOT D'AVERTISSEMENT

La méthode est appelée FirstOrDefault, pas TryFirst. Ce n'est pas là pour vérifier si une valeur existe ou pas, bien que nous tous (ab)utiliser de cette façon.

La création d'une telle méthode d'extension en C# n'est pas que difficile. L'option classique est d'utiliser un paramètre de sortie:

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;
}

L'appel de cette peut être simplifiée en C# 7 :

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

F# les développeurs peuvent se vanter qu'ils ont un Seq.tryPick qui sera de retour None si aucune correspondance n'est trouvée.

C# n'a pas de types d'options ou Peut-être le type (encore), mais peut-être (pun intended), nous pouvons créer notre propre:

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;
}

Qui permet d'écrire la suite de Go-style composez le:

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

En plus de la plus traditionnelle :

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

18voto

Evk Points 17804

Juste pour ajouter une alternative supplémentaire pour gérer les types de valeur et FirstOrDefault : utilisez Where et convertissez le résultat en type nullable:

 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");
 

Vous pouvez même en faire une méthode d'extension:

 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();
    }
}
 

Ensuite, votre code original sera compilé (en supposant que vous remplaciez FirstOrDefault par StructFirstOrDefault ).

7voto

xanatos Points 30513

Comme l'écrit m. Panagiotis vous ne pouvez pas le faire directement... Vous pouvez "tricher" un peu:

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");

Vous prendre jusqu'à un élément avec l' Where et mettre le résultat dans un tableau de longueur de 0-1.

Sinon, vous pouvez répéter la comparaison:

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

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

Cette seconde option ne fonctionnera pas si vous avez été la recherche de

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

Dans ce cas, la valeur par défaut retournée par FirstOrDefault() a a == 0 et b == 0.

Ou vous pouvez simplement créer un "spécial" FirstOrDefault() qui a out bool success (comme les différents 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);
    }
}

utiliser comme:

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

D'autres possibles de la méthode d'extension, ToNullable<>()

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

Utiliser comme:

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

if (result == null)

Notez que result est T?, de sorte que vous aurez besoin de faire d' result.Value pour utiliser sa valeur.

7voto

David Arno Points 15499

Si vous êtes sûr de votre ensemble de données ne comprennent (0, 0, 0), alors que d'autres l'ont dit, vous pouvez vérifier la valeur par défaut:

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

Si cette valeur peut se produire si, ensuite, vous pouvez utiliser First et attraper l'exception quand il n'y a pas de 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");
        }
    }
}

Sinon, vous pouvez utiliser une bibliothèque, comme mon propre Succinc<T> bibliothèque qui fournissent un TryFirst méthode qui retourne un "peut-être" type d' none si pas de match, ou l'article, si en correspondance:

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");
    }
}

6voto

Kevin Sijbers Points 554

Votre chèque pourrait être le suivant:

 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");
}
 

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X