了解LINQ

  • A+
所属分类:.NET技术
摘要

本文主要的是泛谈LINQ是啥?以及常见的用法大纲如下:实际上,能够被foreach的对象,一定是实现了带有返回值的IEnumerator的GetEnumerator()方法的接口,而.NET内置的该接口则是IEnumerable,一般指的是IEnumerable泛型接口,让我们来看看IEnumerator接口有啥成员:

本文主要的是泛谈LINQ是啥?以及常见的用法大纲如下:

  • LINQ的那些根基
  • LINQ的一些基本用法

LINQ的根基

IEnumerable和IEnumerator

为啥能够被foreach?

实际上,能够被foreach的对象,一定是实现了带有返回值的IEnumerator的GetEnumerator()方法的接口,而.NET内置的该接口则是IEnumerable,一般指的是IEnumerable泛型接口,让我们来看看IEnumerator接口有啥成员:

    public interface IEnumerator    {        object Current        {            get;        }         bool MoveNext();         void Reset();    }  
  • Current:集合当前的对象
  • MoveNext:是否能够移动到下一次
  • Reset

因此,实际上我们进行foreach的时候,等价于:

var animals = new List<string>() { "Cat", "Dog", "Pig" };  foreach (var animla in animals) {     Console.WriteLine(animla); } Console.WriteLine("-----------");  var enumerator = animals.GetEnumerator(); while (enumerator.MoveNext()) {     Console.WriteLine(enumerator.Current); }   

输出结果:

Cat Dog Pig ----------- Cat Dog Pig  

而能被LINQ的对象就是一个实现了IEnumerable的可被枚举的集合

LINQ的基本用法

扩展方法在LINQ的应用:LINQ的流式语法

LINQ的方法一般都是通过扩展方法了扩展的,就如最常用的几个,Where,Any,例如,我实现了一个跟Where功能类似的简化版:

  public static class MyListExtension {     public static IEnumerable<T> MyWhere<T>(this IEnumerable<T> enumable, Func<T, bool> func)     {         foreach (var item in enumable)         {             if (func(item))             {                 yield return item;             }         }     } }  

其实为啥会提到用扩展方法呢?就是因为LINQ就是为了简单的能够处理复杂集合的数据,那么扩展方法就能够实现较为简单的链式查询,例如:

 var result= animals.MyWhere(t => t is "Cat" or "Dog").Select(t=>t.ToUpper()).ToList();  result.ForEach(t =>Console.WriteLine(t));   

输出结果:

CAT DOG  

LINQ的查询表达式:LINQ的查询语法

假如上述的例子有LINQ的查询表达式来编写,则写法是这样:

 var result = (from t in animals               where t is "Cat" or "Dog"               select t.ToUpper()).ToList();  result.ForEach(t => Console.WriteLine(t));   

输出结果也是一样的:

CAT DOG  

LINQ的延迟执行:IQueryable

首先我们来看看IQueryable的接口定义:

     public interface IQueryable : IEnumerable     {          Type ElementType         {             get;         }            Expression Expression         {             get;         }          IQueryProvider Provider         {             get;         }     }  

我们可以看到实际上IQueryable是继承了IEnumerable,因此同样具备其特性,然后主要看其三个属性:

  • ElementType:集合的类型
  • Expression:表达式树,这是延迟执行的重点,下面我们会一窥究竟
  • IQueryProvider:IQueryable创建表达式树和执行的部分
     public interface IQueryProvider     {            IQueryable CreateQuery(Expression expression);             IQueryable<TElement> CreateQuery<TElement>(Expression expression);           object? Execute(Expression expression);           TResult Execute<TResult>(Expression expression);     }  

我们先来看段代码:

 var result1 = (from t in animals                 where (t.Equals( "Cat") || t.Equals("Dog"))                 select t.ToUpper()).AsQueryable(); Console.WriteLine($"Expression:{ result1.Expression.ToString()}"); Console.WriteLine($"ExpressionType:{result1.Expression.GetType()}"); foreach (var item in result1) {     Console.WriteLine(item); }  Console.WriteLine("---------------"); var result2 = from t in result1               where  t.Contains("CAT")               select t; Console.WriteLine($"Expression:{ result2.Expression.ToString()}"); Console.WriteLine($"ExpressionType:{result2.Expression.GetType()}"); foreach (var item in result2) {     Console.WriteLine(item); }  

输出如下:

Expression:System.Linq.Enumerable+WhereSelectListIterator`2[System.String,System.String] ExpressionType:System.Linq.Expressions.ConstantExpression CAT DOG --------------- Expression:System.Linq.Enumerable+WhereSelectListIterator`2[System.String,System.String].Where(t => t.Contains("CAT")) ExpressionType:System.Linq.Expressions.MethodCallExpression2 CAT  

我们从输出可以证明,实际上在返回result1和result2,就是通过IQueryProvider不断地在拼接表达式树,而最后通过foreach或者ToList等操作的时候,则才是真正调用Execute方法执行当前的IQueryable里的那个表达式树属性Expression,而像LINQ To Sql或者是EFCore等需要IQueryable这种解释型的就是去实现了IQueryProvider里面的方法

参考

  • 《C#7.0核心技术指南》

源码