How to Assign a Func<> Conditionally Between Lambdas Using the Conditional Ternary Operator

How can I assign a Func using the conditional ternary operator?


var filter = (someBooleanExpressionHere)
? new Func<Something, bool>(x => x.SomeProp < 5)
: x => x.SomeProp >= 5;

How can I assign a Func conditionally between lambdas using the conditional ternary operator?

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.

Assign a lambda expression using the conditional (ternary) operator

The C# compiler tries to create the lambdas independently and cannot unambiguously determine the type. Casting can inform the compiler which type to use:

Action<int> ff = (1 == 2)
? (Action<int>)((int n) => Console.WriteLine("nope {0}", n))
: (Action<int>)((int n) => Console.WriteLine("nun {0}", n));

C# Func and Conditional Operator

The 'type inference' on the conditional operator is not quite good enough, I get a message like

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

you can always just be explicit on the right-hand-side, a la

var o = true ? new Func<int,int>(x => 0) : new Func<int,int>(x => 1);

In any case it's just a minor annoyance regarding how the types of lambdas, type inference, and the conditional operator interact.

Initializing capturing lambda in ternary operator

Every lambda expression has unique type (i.e. the closure type, which is a unique unnamed non-union non-aggregate class type), even with the same signature and function body; the compiler just can't deduce the common type of ternary conditional operator for the variable declared by auto, the two closure types are irrelevant at all.

You can use std::function instead. e.g.

std::function<bool()> l1;
if (condition)
l1 = [](){ return true; };
else
l1 = [number](){ return number == 123; };

For l4, note that the lambda-expression with empty capture list could be converted to the corresponding function pointer implicitly. In this case both of them could be converted to the same function pointer type (i.e. bool(*)(int)), which then could be deduced as the common type of ternary conditional operator and the type of l4.

null-conditional operator doesn't work with Func T inside a generic method

Unfortunately I believe you have hit a edge case of the compiler. The ?. operator needs to return default(RetrunTypeOfRHS) for classes and default(Nullable<RetrunTypeOfRHS>) for structs. Because you have not constrained T to be classes or structs it can't tell which one to promote to.

The reason Action<T> works is because the return type of the right hand side is void for both cases so it does not need to decide which promotion to do.

You will need to use the long form you showed or have two methods with different constraints on T

    public static T TestStruct<T>(Func<T> func) where T : struct
{
return func?.Invoke() ?? default(T);
}

public static T TestClass<T>(Func<T> func) where T : class
{
return func?.Invoke(); // ?? default(T); -- This part is unnecessary, ?. already
// returns default(T) for classes.
}

passing in nullable Expression Func T, TResult ? as method parameter?

There are a few problems with your code

  • Expression<TDelegate> is a class, so it's nullable already; you can simply test if orderBy == null. Nullable<T> has a generic constraint that T must be a struct, so Expression<Func<MyObject, T>>? won't compile.
  • Next you'll have the problem that because the type T isn't bound inside the method, but x.Id is. In other words, you won't be able to create use the conditional operator to choose between a value of Expression<Func<MyObject, T>> and Expression<Func<MyObject, int>> (assuming that Id is an int) while still maintaining type information to pass to the OrderBy method.

The solution is to use something along these lines:

public IPagedList<MyObject> GetAll<T>(Expression<Func<MyObject, T>> orderBy, 
int pageNumber = 1, int pageSize = 10)
{
IQueryable<MyObject> objects = dataContext.MyObjects;
objects = (orderBy != null) ? objects.OrderBy(orderBy)
: objects.OrderBy(x => x.Id);
return objects.ToPagedList<MyObject>(pageNumber, pageSize);
}

The conditional operator works in this code because regardless of what you pass to OrderBy the return type will be the same, IQueryable<MyObject>.

Note also that you can't simply pass in a null value for orderBy, because T can't be inferred. You'd have to call it like this:

var results = MyClass.GetAll<int>(null);

Ultimately, you'd probably be better off creating two overloads, one that accepts an orderBy expression, and one that doesn't.

No implicit conversion between 'lambda expression' and 'lambda expression'?

The type of the conditional expression has to be inferred as a whole - and lambda expressions always have to be converted to a specific delegate or expression tree type.

In your latter two examples, the compiler knows what it's trying to convert the lambda expression to. In the first example, it tries to work out the type of the whole conditional expression first.

A cast in one of the branches would be enough though:

protected override Func<Stream> GetStream()
{
return someBool
? (Func<Stream>)
(() => EmbeddedResourceExtractor.GetFile("SomeFile1.ext"))
: () => EmbeddedResourceExtractor.GetFile("SomeFile2.ext");
}

Sergio's fix (now deleted, but included below) will work if you were happy to evaluate someBool at the time the function is called:

protected override Func<Stream> GetStream()
{
return () => someBool
? EmbeddedResourceExtractor.GetFile("SomeFile1.ext")
: EmbeddedResourceExtractor.GetFile("SomeFile2.ext");
}

Depending on timing, there are all kinds of different ways of fixing the example you've actually given, e.g.

protected override Func<Stream> GetStream()
{
string name = someBool ? "SomeFile1.ext" : "SomeFile2.ext";
return () => EmbeddedResourceExtractor.GetFile(name);
}

I'm guessing your real code is more complicated though.

It's a shame in some ways that C#'s type inference can't be more powerful - but it's already pretty complicated.



Related Topics



Leave a reply



Submit