How can I assign a Func<> conditionally between lambdas using the conditional ternary operator?
Asked Answered
B

4

52

Generally, when using the conditional operator, here's the syntax:

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

Nothing fancy, pretty straight forward.

Now, let's try to use this when assigning a Lambda to a Func type. Let me explain:

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

That's the same syntax, and should work? Right? For some reason that doesn't. The compiler gives this nice cryptic message:

Error 1 Type of conditional expression cannot be determined because there is no implicit conversion between 'lambda expression' and 'lambda expression'

I then went ahead and changed the syntax and this way it did work:

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

I'm just curious as to why it doesn't work the first way?

(Side note: I ended up not needing this code, as I found out that when comparing an int value against null, you just use object.Equals)

Blab answered 4/11, 2008 at 19:39 Comment(0)
G
54

You can convert a lambda expression to a particular target delegate type, but in order to determine the type of the conditional expression, the compiler needs to know the type of each of the second and third operands. While they're both just "lambda expression" there's no conversion from one to the other, so the compiler can't do anything useful.

I wouldn't suggest using an assignment, however - a cast is more obvious:

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

Note that you only need to provide it for one operand, so the compiler can perform the conversion from the other lambda expression.

Grisby answered 4/11, 2008 at 19:45 Comment(6)
I wonder, if the compiler can infer this: Func<Order, bool> predicate = p => p.EmployeeID == id, how come it has trouble inferring this: Func<Order, bool> predicate = id == null ? (Func<Order, bool>) (p => p.EmployeeID == null) : p => p.EmployeeID == id;? I mean it knows the type that is needed for the second and third operands via the declaration of predicate.Kelcy
@GDS: There's an implicit conversion from the lambda expression to the delegate type, which is why the first version works. But the declaration of predicate doesn't affect the type inference for the conditional expression. The language specification basically says that the type of the conditional expression must be inferrable just via the operands.Grisby
Then I wonder, why would the ternary operator have such a requirement. It is just a conditional assignment of the second or third operand to a variable or a conditional evaluation of an expression. Unless I am missing something, any inference achieved with a direct assignment or evaluation should be plausible with the conditional assignment or evaluation too. Moreover if one operant is ambiguous, the inference of the other could potentially resolve the ambiguity. If ambiguity cannot be resolved the compiler can still complain as it does in the above example. Maybe an omitted feature...?Kelcy
@GDS: No, it's not an assignment to a variable, in many cases. Imagine this expression were used as a method argument - you'd then need to involve it all in overloading, which ends up being more and more painful. And the compiler already does use the other operand to resolve ambiguity, which is why the code I've provided in my answer works... I've only cast one operand to Func<Order, bool>; the compiler validates that the other operand can be implicitly converted to the same type.Grisby
I came to this question because I was trying to use a conditional predicate in a .Where(...) linq method call, which is basically the scenario you described. If the compiler can infer one of the operands in .Where(p => p.EmployeeID == id), then why does it have trouble with something like .Where(someBool ? (p => p.EmployeeID == id) : (p => p.EmployeeID == null)). The compiler knows that it should be expecting a Func<Order, bool> so it should be able to do its magic, at least in this case. Furthermore, with two operands it practically has double the amount of clues.Kelcy
@GDS: That's simply not how the language works. I suggest you try to work out how you'd change the language specification to make that work - I'm sure everyone would be happy if you could do so. I suspect you'll find it's more complicated than you expect. Do remember to consider overload resolution in your proposal. I dare say it could be changed to make the conditional expression typeless (like anonymous functions) but I believe that would massively increase the complexity in ways you haven't envisaged.Grisby
S
5

The C# compiler cannot infer the type of the created lambda expression because it processes the ternary first and then the assignment. you could also do:

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

but that just sucks, you could also try

Func<Order, bool> predicate = 
    id == null ? 
        (Order p) => p.EmployeeID == null :
        (Order p) => p.EmployeeID == id;
Stepup answered 4/11, 2008 at 19:46 Comment(1)
The latter doesn't work, because the compiler doesn't know whether to convert to a delegate or an expression tree (or to, say, a Func<Order, object> which would be okay too).Grisby
W
1

Let me have my own example since I had the same problem, too (with the hope that the example be helpful for others):

My Find method is generic method that gets Expression<Func<T, bool>> as predicate and gives List<T> as output.
I wanted to find countries, but I need all of them if language list was empty, and filtered list, if language list was filled. First I used the Code as below:

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

But exactly I get the error :there is no implicit conversion between lambda expression and lambda expression.

The problem was that, we have just two lambda expressions here, and nothing else, for example, what is country => true exactly?? We have to determine the type of at least one of lambda expressions. If just of one of the expressions be determined, then the error will be omitted. But for make the code more readable, I extracted both lambda expressions, and used the variable instead, as below:

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

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

I emphasize that, if I just determined one of the expression's type, the error will be fixed.

Wesley answered 16/1, 2018 at 6:10 Comment(0)
R
1

Just an update - in C# 10, it IS now possible for the compiler to infer the 'natural type' of a lambda, provided that the input type(s) are provided, e.g.

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

This also means that 0 input Funcs and Actions can be inferred:

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

However, this won't work:

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

As a result, it is now possible to do what the OP originally wanted to do, provided that at least the input types are provided, e.g.

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

However, this still isn't permitted:

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

Error : no implicit conversion between 'lambda expression' and 'lambda expression'

Roark answered 2/12, 2021 at 12:29 Comment(1)
13 years later.... I totally forgot about this question, just got a notification of this answer. Yea, C# 10 added some nice features around lambda inference, however it is interesting that the compiler uses the "left" side to help infer the "right" side.Blab

© 2022 - 2024 — McMap. All rights reserved.