C#: 在匿名方法中声明局部变量是否可行?

13

在C#匿名方法中是否可以有局部变量,即在以下代码中我想只执行一次计数。

IQueryable<Enquiry> linq = db.Enquiries;

if(...) linq = linq.Where(...);

if(...) linq = linq.Where(e => 
    (x <= (from p in db.Orders where p.EnquiryId == e.Id select p).Count() && 
        (from p in db.Orders where p.EnquiryId == e.Id select p).Count() <= y));

if(...) linq = linq.Where(...);

var result = (from e in linq select e);
有没有针对匿名函数的"let"关键字?
更新: 请注意,我会在此语句之后添加多个Where子句,因此无法使用Select来结束。
/Niels

你尝试过这个吗:var q = where l in linq let ct = (from p in db.Orders where p.EnquiryId == e.Id select p).Count() select l; - Erik Forbes
@Eric - 你需要从'from'开始,而不是'where'。然后引用了变量'e',但它没有在任何地方定义。还有ct被定义但未使用。最后,即使将'where'更改为'from',结果仍将包含与linq(输入)相同的内容。除此之外,很棒。 - Daniel Earwicker
6个回答

26

可以,为什么不呢?毕竟它只是一个匿名函数!

例如:

 x => { int y = x + 1; return x + y; }

或者,另一种选择是:

 delegate(int x) {
     int y = x + 1;
     return x + y;
 }

那么您的代码可以写成:

  ... = linq.Where(e => {
         var count = (from p in db.Orders where p.EnquiryId == e.Id select p).Count();
         return x <= count && count <= y;
  });

更新:为了澄清关于评论的事情,需要知道匿名方法和Lambda表达式之间的区别。匿名方法就像普通方法一样,没有显式名称。编译时,编译器会为您生成一个带有奇怪名称的普通方法,因此它不会有任何特殊限制。然而,匿名方法的一种表示形式是Lambda表达式。Lambda表达式可以用几种不同的方式解释。第一种是委托。在这种方式下,它们等同于匿名方法。第二种是表达式树。这种方式通常由LINQ to SQL和其他一些LINQ提供程序使用。它们不会直接执行您的表达式。他们将其解析为表达式树,并将树用作输入数据,生成相应的SQL语句在服务器上运行。它不像方法那样执行,并且不被视为匿名方法。在这种情况下,您无法定义局部变量,因为不可能将Lambda解析为表达式树。


那样不行。你会得到错误提示“带有语句体的 Lambda 表达式无法转换为表达式树。” - BFree
问题在于提问者没有正确表达问题 - 他实际上想知道如何在 Linq to SQL 查询的上下文中执行此操作,而不是在 Linq to Objects 查询中执行。很遗憾我们必须要把它们区别对待,但事实就是这样。 - Erik Forbes
你对没有限制的断言是不正确的。Lambda表达式/匿名函数有各种限制。例如,在C#匿名函数中尝试使用base.Something。根据你的版本,你会得到一个警告或者编译失败。 - JaredPar
@Earwicker:在你的例子中,你没有在lambda表达式体中定义局部变量。通过使用let,你没有在lambda中定义局部变量。我说的是lambda表达式,而不是LINQ to SQL中的特性。 - Mehrdad Afshari
@Mehrdad - let 的效果与只读本地变量相同:它捕获并命名计算值,以便在具有明确定义边界的范围内引用。存储实现方式不同,但逻辑上是相同的,这就是为什么它满足提问者的需求。 - Daniel Earwicker
显示剩余3条评论

7

是的,您可以在Linq to objects和Linq to SQL中完全实现您想要的功能。

Linq中有一个let关键字,允许您在查询过程中给中间结果命名,就像您想要的那样。根据您的示例:

... = from e in linq 
      let count = (from p in db.Orders where p.EnquiryId == e.Id select p).Count()
      where (x <= count) && (count <= y)
      select e;

顺便说一下,我认为您原来的例子在语法上有些错误,这在count只是一个名称时更容易发现:

where (x <= count) && /* <= */ (count <= y);

这个不行,我得到了一个语法错误,说查询必须以select结尾。请注意,我需要添加多个Where子句,所以不能结束查询。 - Niels Bosma
我在上面添加了一个“select e”以使示例正确。但是你说的不能结束查询是什么意思?你必须用某些声明其产生内容的语句来结束它。你可以在Where子句中添加多个条件 - 只需添加更多条件即可。 - Daniel Earwicker

2
如果您正在使用Linq to SQL,您将无法使用Mehrdad Afshari的答案。您的LINQ表达式需要是表达式树,而这些不支持匿名委托语法。
您也不能在其他地方创建委托并从lambda内部调用它 - Linq to SQL仅允许在查询体中执行某些操作,而调用委托不是其中之一。
假设您正在使用Linq to SQL(如您的示例所示),则最好的选择是在一个查询中减少计数,然后在需要计数的查询中捕获计数变量。

1

Where方法接受一个Func,所以你在第二部分传递的不是实际的方法,而只是一个bool表达式。我的建议是创建一个实际返回bool值的方法,该方法接受所需的参数,在调用Where方法时,只需这样做:Where(p=> MyMethod(p,...))


0

我遇到了类似的问题。解决方法是创建一个自定义表达式树生成方法。

我在 MSDN 论坛上提出了我的问题。请在这里查看问题和答案:重用 Where 表达式

这可能会给你一个思路,但我必须承认,自定义表达式树并不适合心脏脆弱的人 ;-)


0

如果您有Scheme的一些背景知识,您就会知道'let'只是定义lambda并调用它的语法糖。

因此,有了这个知识,让我们看看如何实现它。

(count => x <= count && count <= y)
  ((from p in db.Orders 
    where p.EnquiryId == e.Id 
    select p).Count())

作为奖励,它看起来也像Scheme :)

免责声明:我没有测试过这个片段,但没有理由它不工作。个人而言,我会使用LINQ提供的“let”结构。

更新:

它不起作用... :(


我猜你的意思是 linq = linq.Where(e => ((count => (soldFrom <= count && count <= soldTo)((from p in db.Orders where p.EnquiryId == e.Id select p).Count())); 但这仍然无法编译... - Niels Bosma
@leppie,问题在于这个问题是关于LINQ to SQL的。不幸的是,这只有标签中指出,而没有在标题或描述中任何地方提到。在LINQ中,“let”关键字并不是直接调用的lambda函数。它是一个Select,产生一系列的输入/输出对。 - Daniel Earwicker

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接