0

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?

Blarghedy
  • 47
  • 8

1 Answers1

2

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.

Guru Stron
  • 102,774
  • 10
  • 95
  • 132
  • The frustrating thing is that if I have `void Foo(int bar)` and `void Foo(double bar)`, then `Foo(5)` calls the first and `Foo(5.5)` calls the second. I suppose that if I have `void Foo(float bar)` instead of `void Foo(int bar)`, then `Foo(5)` would fail because they're both equally valid conversions, which is actually closer to my current situation. – Blarghedy Mar 09 '23 at 16:57
  • That does make me realize that `void SelectItems(Func filterFunc = null, Expression> filterExpression = null)` will accept any lambda expression, cast it to a `Func`, and pass that to `filterFunc`... but you can explicitly pass it to the `filterExpression` with `SelectItems(filterExpression: t => t % 2 == 0)`. Still doesn't really do what I want, which is to implicitly (without any changes anywhere but the function itself) convert it to an `Expression` but it is the next best thing. – Blarghedy Mar 09 '23 at 17:08
  • @Blarghedy _"will accept any lambda expression, cast it to a Func, and pass that to filterFunc"_ - no, there is no implicit conversion between `Expression>` and corresponding `Func<...>`. – Guru Stron Mar 09 '23 at 17:19
  • @Blarghedy that's the rules. This is caused basically by the same principle as why you can't write `var func = t => true;`. _"Foo(5) would fail"_ - actually no, the `float` overload will be used (not sure why though, maybe conversion to float is "better"). – Guru Stron Mar 09 '23 at 17:22
  • Yeah, you're correct - it uses float. The precedence is interesting. It seems to be `nint`, `long`, `float`, and `double` or `decimal` (equal precedence). I assume it prefers `float` because it's the smallest floating number that can accurately store an integer. – Blarghedy Mar 15 '23 at 15:15