Search code examples
c#lambdafunc

Is there a way to have a function (overloaded or otherwise) accept either a Func or an Expression?


The question Can I define a method to accept EITHER a Func OR an Expression<Func>? asks basically the same thing, but it was asked and answered in 2010 and C# has changed quite a bit since then.

I have a filter method, SelectItems, that accepts a Func<Thing, bool>, where Thing is an individual item in a collection and the bool return value is whether or not the item passes the filter.

SelectItems is used in multiple places in the codebase. Some places pass expression lambdas and others pass statement lambdas.

If no items in the collection pass the filter, it throws an exception with an exception message that describes the collection. Because this code is referenced in many places, I would like to add a description of the filter itself to the exception message - something like Item matching filter "(item) => item % 2 == 0" was not found. Items found: 1, 3, 5, 7.

If the filter function accepts an Expression<Func<Thing, bool>>, I can print the function itself with a PrettyPrintCSharpCode method. There are probably other ways to pull information from an Expression, but pretty printing the code would suffice for now. If a Func is passed, though, the message will not be able to include the code. This is fine, and probably the biggest difference between the question I linked above and mine.

If all calls to this function only passed expression lambdas, I could simply change the function argument to an Expression and it would work implicitly. The problem I've run into is that many of the calls pass statement lambdas, so those calls break.

What I would like is to be able to pass either an expression lambda or a statement lambda.

I tried overloading the function (with one overload accepting Func and the other an Expression), but the compiler is unable to properly pass the expression lambdas to the function that accepts an Expression and the statement lambdas to the function that excepts a Func.

I also tried optional arguments (something like SelectItems(Func<Thing, bool> func = null, Expression<Func<Thing, bool>> expr = null)) but it had the same issue.

If I overloaded a function to accept an int or a double, the compiler would figure out the best fit. I'm confused that it doesn't do that for this.

Specifically, the code

class ClassName
    void SelectItems(Expression<Func<Thing, bool>> filterExpression) {}

    void SelectItems(Func<Thing, bool> filterFunc) {}

generates this compiler error

void ClassName.SelectItems(Func<Thing, bool> filterFunc) (+ 1 overload)

CS0121: The call is ambiguous between the following methods or properties:
    'ClassName.SelectItems(Func<Thing, bool>)' and 
    'ClassName.SelectItems(Expression<Func<Thing, bool>>)'

Ambiguous invocation:
    void SelectItems(System.Func<Thing, bool>) (in class ClassName)
    void SelectItems(System.Linq.Expression<System.Func<Thing, bool>>) (in class ClassName)
match

Is there a way to do this?


Solution

  • You can explicitly provide parameter name:

    SelectItems(filterFunc: t => true); // uses Func overload
    SelectItems(filterExpression: t => true); // uses Expression overload
    

    Though it is a bit brittle.

    Another way is to explicitly specify the type:

    SelectItems((Func<Thing, bool>)(t => true))
    SelectItems((Expression<Func<Thing, bool>>)(t => true))
    

    Compiler can disambiguate between the two, problem is that compiler infers the type for lambda expressions like t => true from the context and both Func<T, bool> and Expression<Func<Thing, bool>> are valid in the provided case hence the ambiguity.

    From the specification:

    The result of an expression is classified as one of the following:

    • ...
    • An anonymous function. An expression with this classification can be implicitly converted to a compatible delegate type or expression tree type.
    • ...

    and

    The evaluation of an anonymous-function conversion depends on the target type of the conversion: If it is a delegate type, the conversion evaluates to a delegate value referencing the method that the anonymous function defines. If it is an expression-tree type, the conversion evaluates to an expression tree that represents the structure of the method as an object structure.