52 votes

Comment affecter un Func<> conditionnellement entre lambdas en utilisant l'opérateur ternaire conditionnel ?

En général, lorsqu'on utilise l'opérateur conditionnel, voici la syntaxe :

int x = 6;
int y = x == 6 ? 5 : 9;

Rien d'extraordinaire, c'est assez simple.

Maintenant, essayons de l'utiliser en assignant une Lambda à un type Func. Laissez-moi vous expliquer :

Func<Order, bool> predicate = id == null
    ? p => p.EmployeeID == null
    : p => p.EmployeeID == id;

C'est la même syntaxe, et devrait travail ? Pas vrai ? Pour une raison quelconque, ça ne marche pas. Le compilateur donne ce joli message cryptique :

Erreur 1 Le type d'expression conditionnelle ne peut être déterminé car il n'y a pas de conversion implicite entre 'expression lambda' et 'expression lambda'.

J'ai ensuite modifié la syntaxe et de cette façon, il y a a fait travail :

Func<Order, bool> predicate = id == null
    ? predicate = p => p.EmployeeID == null
    : predicate = p => p.EmployeeID == id;

Je suis juste curieux de savoir pourquoi ça ne marche pas de la première façon ?

(Note annexe : je n'ai finalement pas eu besoin de ce code, car j'ai découvert que pour comparer une valeur int à null, il suffit d'utiliser object.Equals).

54voto

Jon Skeet Points 692016

Vous pouvez convertir une expression lambda en un type de délégué cible particulier, mais pour déterminer le type de l'expression conditionnelle, le compilateur doit connaître le type de chacun des deuxième et troisième opérandes. Bien qu'il s'agisse simplement d'une "expression lambda", il n'y a pas de conversion de l'une à l'autre, et le compilateur ne peut donc rien faire d'utile.

Je ne suggérerais pas d'utiliser une affectation, cependant - un casting est plus évident :

Func<Order, bool> predicate = id == null 
    ? (Func<Order, bool>) (p => p.EmployeeID == null)
    : p => p.EmployeeID == id;

Notez que vous ne devez le fournir que pour un seul opérande, afin que le compilateur puisse effectuer la conversion à partir de l'autre expression lambda.

5voto

Jake Points 1900

Le compilateur C# ne peut pas déduire le type de l'expression lambda créée car il traite d'abord le ternaire et ensuite l'affectation. vous pourriez également le faire :

Func<Order, bool> predicate = 
    id == null ? 
        new Func<Order,bool>(p => p.EmployeeID == null) :
        new Func<Order,bool>(p => p.EmployeeID == id);

mais ça craint, vous pouvez aussi essayer

Func<Order, bool> predicate = 
    id == null ? 
        (Order p) => p.EmployeeID == null :
        (Order p) => p.EmployeeID == id;

1voto

Elnaz Points 1579

Permettez-moi de donner mon propre exemple, car j'ai eu le même problème (avec l'espoir que l'exemple soit utile à d'autres) :

Mon Find est une méthode générique qui obtient Expression<Func<T, bool>> comme prédicat et donne List<T> comme sortie.
Je voulais trouver des pays, mais j'avais besoin de tous les pays si la liste des langues était vide, et d'une liste filtrée, si la liste des langues était remplie. J'ai d'abord utilisé le code suivant :

var countries= 
Find(languages.Any() 
  ? (country => languages.Contains(country.Language))
  : (country => true));

Mais je reçois exactement l'erreur : there is no implicit conversion between lambda expression and lambda expression.

Le problème était que, nous avons juste deux expressions lambda ici, et rien d'autre, par exemple, ce qui est country => true exactement ? Nous devons déterminer le type de au moins une des expressions lambda . Si une seule des expressions est déterminée, alors l'erreur sera omise. Mais pour rendre le code plus lisible, j'ai extrait les deux expressions lambda, et utilisé la variable à la place, comme ci-dessous :

   Expression<Func<Country, bool>> getAllPredicate = country => true;
   Expression<Func<Country, bool>> getCountriesByLanguagePredicate = country => languages.Contains(country.Language);

   var countries= Find(languages.Any()
                       ? getCountriesByLanguagePredicate
                       : getAllPredicate);

J'insiste sur le fait que, si je détermine simplement un des types d'expression, l'erreur sera corrigée.

1voto

StuartLC Points 35534

Juste une mise à jour - en C# 10, il est maintenant possible pour le compilateur de déduire le nom de l'utilisateur de l'utilisateur. le "type naturel" d'un lambda à condition que le ou les types d'entrée soient fournis, par exemple

var evenFilter = (int i) => i % 2 == 0; // evenFilter inferred as `Func<int, bool>`

Cela signifie également qu'il n'est pas possible de déduire les fonctions et les actions d'entrée :

var zeroInputFunc = () => 44 % 2 == 0;
var myAction = () => {Console.WriteLine("Foo");};

Cependant, cela ne fonctionnera pas :

var filter = i => i % 2 == 0; << Error: The delegate type could not be inferred

En conséquence, il est maintenant possible de faire ce que le PO voulait faire à l'origine, à condition qu'au moins les types d'entrée soient fournis, par ex.

Func<int, bool> myPredicate = selectorFlag
    ? i => i % 2 == 0
    : i => i % 2 == 1;

Cependant, cela n'est toujours pas autorisé :

var myPredicate = selectorFlag
    ? (int i) => i % 2 == 0
    : (int i) => i % 2 == 1;

Erreur : pas de conversion implicite entre 'expression lambda' et 'expression lambda'.

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