有办法让一个函数(重载或其他方式)接受 Func 或 Expression 吗?

huangapple go评论77阅读模式
英文:

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

问题

问题我是否可以定义一个方法来同时接受Func<T>和Expression<Func<T>>?基本上是相同的,但它是在2010年提出并得到回答的,而且自那时以来C#已经发生了很大的变化。

我有一个名为SelectItems的过滤方法,它接受一个Func&lt;Thing, bool&gt;,其中Thing是集合中的单个项目,bool返回值表示项目是否通过了过滤器。

SelectItems在代码库中的多个位置都被使用。有些地方传递表达式lambda,而其他地方传递语句lambda。

如果集合中没有项目通过过滤器,它会抛出一个异常,并且异常消息描述了集合。因为这段代码在很多地方被引用,我想在异常消息中添加过滤器本身的描述 - 比如说 匹配过滤器的项目 "(item) => item % 2 == 0" 未找到。找到的项目:1, 3, 5, 7

如果过滤函数接受一个Expression&lt;Func&lt;Thing, bool&gt;&gt;,我可以使用PrettyPrintCSharpCode方法打印函数本身。可能还有其他从Expression中提取信息的方法,但是目前漂亮地打印代码就足够了。然而,如果传递的是Func,那么消息将无法包含代码。这没关系,这可能是我链接的问题和我的问题之间最大的区别。

如果所有对这个函数的调用都只传递表达式lambda,我可以简单地将函数参数更改为Expression,它将隐式地起作用。我遇到的问题是,很多调用传递了语句lambda,所以这些调用会失败。

我尝试过载这个函数(一个接受Func,另一个接受Expression),但是编译器无法正确地将表达式lambda传递给接受Expression的函数,将语句lambda传递给接受Func的函数。

我还尝试了可选参数(类似于 SelectItems(Func&lt;Thing, bool&gt; func = null, Expression&lt;Func&lt;Thing, bool&gt;&gt; expr = null))但是它有相同的问题。

如果我重载一个函数来接受int或者double,编译器会找到最佳匹配。我感到困惑的是,它在这种情况下没有这样做。

具体来说,下面的代码

class ClassName
	void SelectItems(Expression&lt;Func&lt;Thing, bool&gt;&gt; filterExpression) {}

	void SelectItems(Func&lt;Thing, bool&gt; filterFunc) {}

会生成这个编译器错误

void ClassName.SelectItems(Func&lt;Thing, bool&gt; filterFunc) (+ 1 overload)

CS0121: 此调用在以下方法或属性之间不明确: 
    'ClassName.SelectItems(Func&lt;Thing, bool&gt;)'  
    'ClassName.SelectItems(Expression&lt;Func&lt;Thing, bool&gt;&gt;)'

不明确的调用:
	void SelectItems(System.Func&lt;Thing, bool&gt;) (在类 ClassName )
	void SelectItems(System.Linq.Expression&lt;System.Func&lt;Thing, bool&gt;&gt;) (在类 ClassName )
match

有没有一种方法可以做到这一点?

英文:

The question Can I define a method to accept EITHER a Func<T> OR an Expression<Func<T>>? 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&lt;Thing, bool&gt;, 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 &quot;(item) =&gt; item % 2 == 0&quot; was not found. Items found: 1, 3, 5, 7.

If the filter function accepts an Expression&lt;Func&lt;Thing, bool&gt;&gt;, 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&lt;Thing, bool&gt; func = null, Expression&lt;Func&lt;Thing, bool&gt;&gt; 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&lt;Func&lt;Thing, bool&gt;&gt; filterExpression) {}

	void SelectItems(Func&lt;Thing, bool&gt; filterFunc) {}

generates this compiler error

void ClassName.SelectItems(Func&lt;Thing, bool&gt; filterFunc) (+ 1 overload)

CS0121: The call is ambiguous between the following methods or properties:
    &#39;ClassName.SelectItems(Func&lt;Thing, bool&gt;)&#39; and 
    &#39;ClassName.SelectItems(Expression&lt;Func&lt;Thing, bool&gt;&gt;)&#39;

Ambiguous invocation:
	void SelectItems(System.Func&lt;Thing, bool&gt;) (in class ClassName)
	void SelectItems(System.Linq.Expression&lt;System.Func&lt;Thing, bool&gt;&gt;) (in class ClassName)
match

Is there a way to do this?

答案1

得分: 2

你可以明确提供参数名称:

SelectItems(filterFunc: t => true); // 使用 Func 重载
SelectItems(filterExpression: t => true); // 使用 Expression 重载

虽然这种方式有点脆弱。

另一种方法是明确指定类型:

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

编译器可以区分这两者之间的差异,问题在于编译器会从上下文中推断 lambda 表达式 t => true 的类型,而在提供的情况下,Func<T, bool>Expression<Func<Thing, bool>> 都是有效的,因此存在歧义。

来自规范

> 表达式的结果被分类为以下之一:
> - ...
> - 匿名函数。具有此分类的表达式可以隐式转换为兼容的委托类型或表达式树类型。
> - ...

> 匿名函数转换的评估取决于转换的目标类型:如果它是委托类型,则转换评估为引用匿名函数定义的方法的委托值。如果它是表达式树类型,则转换评估为表示该方法结构的表达式树,该结构作为对象结构。

英文:

You can explicitly provide parameter name:

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

Though it is a bit brittle.

Another way is to explicitly specify the type:

SelectItems((Func&lt;Thing, bool&gt;)(t =&gt; true))
SelectItems((Expression&lt;Func&lt;Thing, bool&gt;&gt;)(t =&gt; true))

Compiler can disambiguate between the two, problem is that compiler infers the type for lambda expressions like t =&gt; true from the context and both Func&lt;T, bool&gt; and Expression&lt;Func&lt;Thing, bool&gt;&gt; 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.

huangapple
  • 本文由 发表于 2023年3月10日 00:23:51
  • 转载请务必保留本文链接:https://go.coder-hub.com/75687371.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定