本文主要的是泛談LINQ是啥?以及常見的用法大綱如下:
- LINQ的那些根基
- LINQ的一些基本用法
LINQ的根基
IEnumerable和IEnumerator
為啥能夠被foreach?
實際上,能夠被foreach的物件,一定是實現了帶有返回值的IEnumerator的GetEnumerator()方法的介面,而.NET內建的該介面則是IEnumerable,一般指的是IEnumerable
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核心技術指南》