LINQ中的性能和概念问题

时间:2015-06-05 06:16:31

标签: c# linq

我在项目中使用LINQ有一些问题/疑虑。第一个问题是 - 旧(select Item from..)linq和新版本(.Select(r => ..))之间的性能是否存在差异?

第二个问题,如何翻译LINQ表达(以及在什么中)?它会先翻译成旧语法,然后翻译成其他东西(中间语言)吗?

2 个答案:

答案 0 :(得分:6)

我们可以编写linq查询的两种方式之间没有任何区别。

具体来说,这个

var adults = from customer in customers
             where customer.Age>18
             select customer;

相当于:

var adults = customers.Where(customer=>customer.Age>18);

实际上,编译器会将第一个查询转换为第二个查询。编写linq查询的第一种方法就像语法糖。在引擎盖下,如果您编译代码然后使用dissasembler查看IL代码,您会注意到您的查询已被翻译为上述第二种形式。

使用第一种方式编写的查询,我们说我们已经使用了查询语法。在使用第二种方式编写查询时,我们说我们使用了流畅的语法

答案 1 :(得分:1)

  

旧版本(select Item from..)和新版本(.Select(r => ..))之间的性能是否存在差异?

这些都不比另一个年长,因为两者同时进入语言。如果任何.Select()可以被认为是较旧的,那么方法调用几乎总是调用扩展方法(因此只能从.NET 3.5开始,并且只能用C#3.0调用),一般都有方法调用从1.0开始。

表现没有区别,因为他们说同样的话是不同的方式。 (它可能是你可以找到一个案例导致一个但不是另一个的冗余,但大多数情况下这些冗余被编译器捕获并被删除)。

  

如何翻译LINQ表达式(以及在何处)?它会先翻译成旧语法,然后翻译成其他东西(中间语言)吗?

考虑到,根据上述情况,from item in someSource select item.IDsomeSouce.Select(item => item.ID)是相同的。编译器必须做两件事:

  1. 确定应如何进行通话。
  2. 确定如何使用lambda。
  3. 这两者齐头并进。第一部分与任何其他方法调用相同:

    1. 查找在someSource类型上定义的名为Select()的方法,并采用适当类型的一个参数(我将来到"适当的类型& #34;一分钟内。

    2. 如果找不到方法,请查找在someSource类型的直接基础上定义的方法,依此类推,直到您没有更多要检查的基类(到达{{1}之后) })。

    3. 如果没有找到方法,请查找在静态类上定义的扩展方法,该类可以通过object使用,其第一个(using)参数的类型为{ {1}},以及我说的相应类型的第二个参数我将在一分钟后回来。

    4. 如果找不到方法,请查找可以接受this类型和lambda作为参数的通用扩展方法。

    5. 如果没有找到方法,请对someSource的基本类型及其实现的接口执行以上两个步骤,继续进一步扩展这些接口的基类型或接口。

    6. 如果未找到方法,则引发编译器错误。同样,如果上述任何步骤在同一步骤中发现两个或更多同等适用方法 ,则会引发编译错误。

    7. 到目前为止,这与someSource调用someSource上定义的"".IsNormalized()方法的方式相同,IsNormalized()调用string上定义的"".GetHashCode()方法(虽然后面的步骤表示GetHashCode()上定义的覆盖是实际执行的内容),object调用string上定义的"".GetType()方法。

      事实上,我们可以在下面看到这一点:

      GetType()

      此处因为object有自己适用的public class WeirdSelect { public int Select<T>(Func<WeirdSelect, T> ignored) { Console.WriteLine("Select‎ Was Called"); return 2; } } void Main() { int result = from whatever in new WeirdSelect() select whatever; } 方法,而不是WeirdSelectSelect中定义的扩展方法之一。

      现在,我手动挥动了#34;相应类型的参数&#34;上面因为lambdas带来的一个复杂因素是C#代码中的lambda可以变成委托(在这种情况下是Enumerable,其中Queryable是lambdas参数的类型而Func<TSource, TResult> 1}}它返回的值的类型)或生成的CIL代码中的表达式(在本例中为TSource)。

      因此,方法调用解析正在寻找接受TResult(或类似委托)的方法或接受Expression<Func<TSource, TResult>>(或类似表达式)的方法。如果它在搜索的同一阶段发现,则会出现编译错误,因此以下操作无效:

      Func<TSource, TResult>

      现在,99.999%的时间我们使用Expression<Func<TSource, TResult>>来实现public class WeirdSelect { public int Select<T>(Func<WeirdSelect, T> ignored) { Console.WriteLine("Select‎ Was Called"); return 2; } public int Select<T>(Expression<Func<WeirdSelect, T>> ignored) { Console.WriteLine("Select‎ Was Called on expression"); return 1; } } void Main() { int result = from whatever in new WeirdSelect() select whatever; } 或实现select的东西。如果它实现IQueryable<T>,则方法调用解析将在IEnumerable<T>中找到IQueryable<T>,如果它实现public static IQueryable<TResult> Select<TSource, TResult>(this IQueryable<TSource> source, Expression<Func<TSource, TResult>> selector),则会在Queryable中找到IEnumerable<T> }。 public static IEnumerable<TResult> Select<TSource, TResult>(this IEnumerable<TSource> source, Func<TSource, TResult> selector)派生自Enumerable并不重要,因为在IQueryable<T>被视为基本接口之前,其方法将在上述过程的早期步骤中找到。< / p>

      因此,99.999%的时间会对这两种扩展方法之一进行调用。在IEnumerable<T>的情况下,lambda变成了一些产生适当IEnumerable<T>的代码,然后传递给该方法(然后查询引擎能够将其转换为适当的代码,例如创建适当的SQL查询是否为数据库支持的查询引擎,否则为其他内容。在IQueryable<T>的情况下,lamda被转换为一个匿名委托,它被传递给有点像这样的方法:

      Expression

      回到你的问题:

        

      首先将它翻译成旧语法然后翻译成其他语言(中间语言)吗?

      您可以将较新的IEnumerable<T>语法视为&#34;变为&#34;较旧的public static IEnumerable<TResult> Select<TSource, TResult>(this IEnumerable<TSource> source, Func<TSource, TResult> selector) { //Simplifying a few things, this code is to show the idea only foreach(var item in source) yield return selector(item); } 语法(但不是很老,因为它在99%的时间内依赖于扩展方法),因为它使方法调用更清晰,但实际上它们相同。在CIL中产生的差异取决于调用是实例方法还是(几乎总是如此)扩展方法,甚至更多是关于lambda是用于生成表达式还是委托。