瞭解LINQ

RyzenAdorer發表於2021-12-11

本文主要的是泛談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核心技術指南》

原始碼