如何使用条件三元运算符在 lambda 之间有条件地分配 Func<>?
Posted
技术标签:
【中文标题】如何使用条件三元运算符在 lambda 之间有条件地分配 Func<>?【英文标题】:How can I assign a Func<> conditionally between lambdas using the conditional ternary operator? 【发布时间】:2010-09-20 18:14:38 【问题描述】:一般情况下,使用条件运算符时,语法如下:
int x = 6;
int y = x == 6 ? 5 : 9;
没什么花哨的,很直接。
现在,让我们尝试在将 Lambda 分配给 Func 类型时使用它。让我解释一下:
Func<Order, bool> predicate = id == null
? p => p.EmployeeID == null
: p => p.EmployeeID == id;
语法相同,应该有效吗?正确的?出于某种原因,没有。编译器给出了这个很好的神秘信息:
错误 1 无法确定条件表达式的类型,因为“lambda 表达式”和“lambda 表达式”之间没有隐式转换
然后我继续更改语法,这样它确实工作了:
Func<Order, bool> predicate = id == null
? predicate = p => p.EmployeeID == null
: predicate = p => p.EmployeeID == id;
我只是好奇为什么第一种方式不起作用?
(旁注:我最终不需要此代码,因为我发现在将 int 值与 null 进行比较时,您只需使用 object.Equals)
【问题讨论】:
【参考方案1】:您可以将 lambda 表达式转换为特定的目标委托类型,但为了确定条件表达式的类型,编译器需要知道第二个和第三个操作数的类型。虽然它们都是“lambda 表达式”,但没有从一个到另一个的转换,所以编译器不能做任何有用的事情。
我不建议使用赋值,但是 - 演员表更明显:
Func<Order, bool> predicate = id == null
? (Func<Order, bool>) (p => p.EmployeeID == null)
: p => p.EmployeeID == id;
请注意,您只需为一个操作数提供它,因此编译器可以执行从另一个 lambda 表达式的转换。
【讨论】:
我想知道,如果编译器可以推断出这个:Func<Order, bool> predicate = p => p.EmployeeID == id
,为什么它很难推断出这个:Func<Order, bool> predicate = id == null ? (Func<Order, bool>) (p => p.EmployeeID == null) : p => p.EmployeeID == id;
?我的意思是它通过predicate
的声明知道第二个和第三个操作数所需的类型。
@GDS:从 lambda 表达式到委托类型的隐式转换,这就是第一个版本有效的原因。但是predicate
的声明不影响条件表达式的类型推断。语言规范基本上说条件表达式的类型必须是可以通过操作数推断出来的。
那我就纳闷了,为什么三元算子会有这样的要求。它只是将第二个或第三个操作数条件赋值给变量或表达式的条件评估。除非我遗漏了某些东西,否则通过直接分配或评估实现的任何推断都应该通过条件分配或评估来实现。此外,如果一个操作符是模棱两可的,则另一个操作符的推断可能会解决模棱两可的问题。如果无法解决歧义,编译器仍然会像上面的示例一样抱怨。也许是一个省略的功能......?
@GDS:不,在很多情况下,这不是对变量的赋值。想象一下这个表达式被用作方法参数 - 然后你需要将它全部参与重载,这最终会变得越来越痛苦。并且编译器已经确实使用另一个操作数来解决歧义,这就是我在答案中提供的代码有效的原因......我只投了 one 操作数给Func<Order, bool>
;编译器验证另一个操作数是否可以隐式转换为相同的类型。
我来这个问题是因为我试图在.Where(...)
linq 方法调用中使用条件谓词,这基本上就是你描述的场景。如果编译器可以推断出.Where(p => p.EmployeeID == id)
中的操作数之一,那么为什么它会遇到.Where(someBool ? (p => p.EmployeeID == id) : (p => p.EmployeeID == null))
之类的问题。编译器知道它应该期待一个Func<Order, bool>
,所以它应该能够发挥它的魔力,至少在这种情况下是这样。此外,如果有两个操作数,它实际上有双倍的线索。【参考方案2】:
C# 编译器无法推断创建的 lambda 表达式的类型,因为它先处理三元,然后再处理赋值。你也可以这样做:
Func<Order, bool> predicate =
id == null ?
new Func<Order,bool>(p => p.EmployeeID == null) :
new Func<Order,bool>(p => p.EmployeeID == id);
但这很糟糕, 你也可以试试
Func<Order, bool> predicate =
id == null ?
(Order p) => p.EmployeeID == null :
(Order p) => p.EmployeeID == id;
【讨论】:
后者不起作用,因为编译器不知道是转换为委托还是表达式树(或者说转换为 Func因为我也有同样的问题,所以我举个自己的例子(希望这个例子对其他人有帮助):
我的 Find
方法是通用方法,它获取 Expression<Func<T, bool>>
作为谓词并给出 List<T>
作为输出。
我想找到国家,但如果语言列表为空,我需要所有国家,如果语言列表已填满,我需要过滤列表。
首先我使用的代码如下:
var countries=
Find(languages.Any()
? (country => languages.Contains(country.Language))
: (country => true));
但我确实得到了错误:there is no implicit conversion between lambda expression and lambda expression.
问题是,我们这里只有两个 lambda 表达式,没有别的,例如,country => true
到底是什么?我们必须确定至少一个 lambda 表达式的类型。如果仅确定其中一个表达式,则将省略错误。但是为了让代码更具可读性,我提取了两个 lambda 表达式,并改用了变量,如下所示:
Expression<Func<Country, bool>> getAllPredicate = country => true;
Expression<Func<Country, bool>> getCountriesByLanguagePredicate = country => languages.Contains(country.Language);
var countries= Find(languages.Any()
? getCountriesByLanguagePredicate
: getAllPredicate);
我强调,如果我只是确定了表达式的类型之一,错误就会得到修复。
【讨论】:
【参考方案4】:只是一个更新 - 在 C# 10 中,编译器现在可以推断 'natural type' of a lambda,前提是提供了输入类型,例如
var evenFilter = (int i) => i % 2 == 0; // evenFilter inferred as `Func<int, bool>`
这也意味着可以推断出0个输入Funcs和Actions:
var zeroInputFunc = () => 44 % 2 == 0;
var myAction = () => Console.WriteLine("Foo");;
但是,这不起作用:
var filter = i => i % 2 == 0; << Error: The delegate type could not be inferred
因此,现在可以执行 OP 最初想要执行的操作,前提是至少提供输入类型,例如
Func<int, bool> myPredicate = selectorFlag
? i => i % 2 == 0
: i => i % 2 == 1;
但是,这仍然是不允许的:
var myPredicate = selectorFlag
? (int i) => i % 2 == 0
: (int i) => i % 2 == 1;
错误:“lambda 表达式”和“lambda 表达式”之间没有隐式转换
【讨论】:
13 年后......我完全忘记了这个问题,只是收到了这个答案的通知。是的,C# 10 围绕 lambda 推断添加了一些不错的功能,但是编译器使用“左侧”来帮助推断“右侧”,这是有趣的。以上是关于如何使用条件三元运算符在 lambda 之间有条件地分配 Func<>?的主要内容,如果未能解决你的问题,请参考以下文章