了解LINQ


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

源碼


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM