.NET進階篇05-Linq、Lambda表達式

知識須要不斷積累、總結和沉澱,思考和寫做是成長的催化劑javascript

內容目錄

1、Lambda表達式一、匿名方法二、Lambda表達式2、Linq概述3、查詢操做符一、linq初見二、經常使用查詢操做符篩選排序分組鏈接合併分頁聚合轉換4、並行Linq5、表達式樹一、認識表達式目錄樹二、拼裝表達式樹三、應用6、小結php

1、Lambda表達式

一、匿名方法

使用delegate的時候不少時候不必使用一個普通方法,由於這個方法只有delegate會用,而且只用一次,這時候使用匿名方法最合適。
匿名方法就是沒有名字的方法。示例中就是用一個匿名方法建立委託實例,咱們無需在寫一個具名方法給委託,使代碼更簡潔和可讀。匿名方法也是在調用時執行,在myDele(1,"test")處調用。(用反編譯器看一下仍是會生成一個具名方法的,只不過在編譯器內部使用)。java

delegate bool MyDelegate(int i, string s);
MyDelegate myDele = delegate (int i, string s)
{
    Console.WriteLine($"我是匿名方法,參數值{i}{s}");
    return true;
};
bool b = myDele(1"test");

二、Lambda表達式

函數式編程,在C#3.0開始,咱們有了Lambda表達式代替匿名方法,它比匿名方法更加簡單。Lambda運算符「=>」(發音goesto)的左邊列出了須要的參數,右邊是利用該參數方法的實現代碼。nginx

Action<string> a1 = delegate (string s) { Console.WriteLine(s); };
a1("匿名方法");
Action<string> a2 =  (string s)=> { Console.WriteLine(s); };
a1("Lambda表達式");
Action<string> a3 = s => { Console.WriteLine(s); };
a3("Lambda表達式,有一個參數的能夠簡寫不要小括號,參數類型會自動推斷");
Action<string> a4 = s =>  Console.WriteLine(s);
a4("Lambda表達式,方法體只有一行,連花括號也能夠省略");

另外一點,經過Lambda表達式能夠訪問Lambda表達式塊外部的變量。這是一個很是好的功能,但若是未正確使用,也會很是危險。web

int sommVal = 5;
Func<intint> f = x => x + sommVal;

sommVal = 7;
Console.WriteLine(f(3));

若是外部修改了sommVol值就會影響Lambda表達式的輸出,特別是在多線程中,可能沒法肯定當前的sommVal值。
Lambad表達式內部是如何使用外部的變量呢?首先編譯器會建立一個匿名類,而後將使用到的外部變量當作匿名類的構造函數的參數,當調用時候,就建立匿名類的一個實例,並傳遞調用該方法時外部變量的值。sql

2、Linq概述

Linq(language integrated query)語言集成查詢集成了C#編程語言中的查詢語法,使之可使用相同的語法訪問不一樣的數據源
根據數據源的不一樣,Linq可分爲linq to object,linq to sql,linq to xml,你也能夠擴展linq to excel,to everything。爲不一樣的數據源提供相同的查詢接口便可。數據庫

3、查詢操做符

一、linq初見

如今咱們有以下實體的集合express

public class Student
{
    public int Id { getset; }
    public int ClassId { getset; }
    public string Name { getset; }
    public int Age { getset; }
}
List<Student> studentLst = new List<Student>();

假設studentLst裏已經有一些數據,而後須要查詢出年紀小於25的學生。有不少方法,能夠循環列表挑出age<25的學生,可使用List的FindAll,Where等方法,看起來像下面這樣(注意只有在訪問list中數據時,纔會去執行過濾條件查詢,延遲查詢)編程

var list = studentLst.Where<Student>(s => s.Age < 25); 
foreach (var item in list)
{
    Console.WriteLine("Name={0}  Age={1}", item.Name, item.Age);
}

where擴展方法的內部邏輯大概像這樣,foreach循環調用過濾的委託方法,yield關鍵字語法糖包裝了一些複雜行爲,包括會初始化一個IEnumerable類,而後給添加內容。緩存

public static IEnumerable<T> Where<T>(this IEnumerable<T> source, Func<T, bool> func)
{
    if (source == null)
    {
        throw new Exception("source is null");
    }
    if (func == null)
    {
        throw new Exception("func is null");
    }

    foreach (var item in source)
    {
        if (func.Invoke(item))
        {
            yield return item;
        }
    }
}

那麼用Linq如何查詢呢?

var list = from s in studentList
           where s.Age < 25
           select s;

foreach (var item in list)
{
    Console.WriteLine("Name={0}  Age={1}", item.Name, item.Age);
}

From、where、select都是預約義的關鍵字,查詢表達式必須以from開頭,以select或group子句結束,中間可使用where、orderby、join等。from子句引入數據源studentList和範圍變量s,s就像foreach循環中的迭代變量。
一樣的,在運行期間定義查詢表達式時,查詢不會當即運行,在迭代數據項時運行。

二、經常使用查詢操做符

篩選

最多見的查詢操做就是以布爾表達式的形式應用篩選器。經過where子句篩選表達式爲true的結果

var list = from s in studentList
           where s.Age < 25 && s.ClassId==1
           select s;
排序

Orderby子句根據要排序類型的默認比較器,對返回序列中的元素進行排序

var list = from s in studentList
           where s.Age < 25
           orderby s.Name ascending
           select s;

和list的如下方法相似,就是把關鍵字解析爲方法,隨着查詢愈來愈複雜,linq這種相似sql語句就表現的更加簡潔直觀

var list = studentList.Where(s => s.Age < 25).OrderByDescending(s => s.Name).Select(s => s);
分組

group 子句用於對根據您指定的鍵所得到的結果進行分組。示例中into關鍵字建立進一步查詢的標識,用select子句建立了一個帶key和maxAge屬性的匿名類型,返回每一個班級中年齡小於25歲的最大年齡。

var list = from s in studentList
           where s.Age < 25
           group s by s.ClassId into sg
           select new
           {
               key = sg.Key,
               maxAge = sg.Max(t => t.Age)
           };
foreach (var item in list)
{
    Console.WriteLine($"key={item.key}  maxAge={item.maxAge}");
}
鏈接

使用join子句能夠根據特性的條件合併兩個數據源。例如經過鏈接查詢選擇相同課程的學生

List<Class> classList = new List<Class>(){
        new Class()
        {
            Id=1,
            ClassName="高數"
        },
        new Class()
        {
            Id=2,
            ClassName="毛概"
        }
};
var list = from s in studentList
           join c in classList on s.ClassId equals c.Id
           select new
           {
               Name = s.Name,
               CalssName = c.ClassName
           };
foreach (var item in list)
{
    Console.WriteLine($"Name={item.Name},CalssName={item.CalssName}");
}
合併

zip方法是.NET4新增的,容許用一個函數把兩個序列合併爲一個。第一個集合中的第一項會與第二個集合中的第一項合併,第一個集合中的第二項與第二個集合中的第二項合併,以此類推。若是兩個集合的項目不一樣,zip方法就在到達較小集合的末尾時中止

var list = from s in studentList
           where s.Age < 25
           select s;
var list2 = from s in studentList
            where s.Age < 25
            select s;
var lst = list.Zip(list2, (first, second) => first.Name + "," + second.Name);
分頁

擴展方法Take()和Skip()等的分區操做用於分頁。使用時把擴展方法take、skip添加到查詢的最後,skip方法會忽略根據頁面大小和實際頁數計算出的項數,再使用take方法根據頁面大小提取必定數量的項

int pageSize = 5;
int pageIdx = 0;
var list = (from s in studentList
            where s.Age < 25
            select s).Skip(pageIdx * pageSize).Take(pageSize);
聚合

聚合操做符Count(),Sum(),Min(),Average()等不返回一個序列,而返回一個值。

轉換

Linq不僅是檢索數據。 它也是用於轉換數據的強大工具。 經過使用 LINQ 查詢,可使用源序列做爲輸入,並經過多種方式對其進行修改,以建立新的輸出序列。 經過排序和分組,你能夠修改序列自己,而無需修改這些元素自己。 但也許 LINQ 查詢最強大的功能是建立新類型
如下示例將內存中數據結構中的對象轉換爲 XML 元素。

var studentsToXML = new XElement("Root",
            from student in studentList
            select new 
            XElement("student",
                    new XElement("name", student.Name),
                    new XElement("age", student.Age)
    )
);
Console.WriteLine(studentsToXML);

4、並行Linq

.NET4在System.Linq名稱空間中包含了一個新類ParallelEnumerable,能夠分解查詢的工做使其分佈在多個線程上。集合序列會分紅多個部分,不一樣的線程處理,完成後合併。這對大集合,又是多核CPU的能夠提升效率

var list = (from s in studentList.AsParallel()
           where s.Age < 25
           select s.Age).Sum();
var list2 = (from s in Partitioner.Create(studentList,true).AsParallel().WithDegreeOfParallelism(8)
            where s.Age < 25
            select s.Age).Sum();

可使用Partitioner類建立分區器,WithDegreeOfParallelism指定最大並行任務數
並行linq每每須要較多耗時使用,那應該也有取消長時間運行的任務需求。給查詢添加一個WithCancellation方法,並傳遞一個CancellationToken令牌做爲參數。該查詢在單獨線程中使用,主線程中觸發取消命令。

var cts = new CancellationTokenSource();
new Thread(()=>
    {
        try
        {
            var sum= (from s in studentList.AsParallel().WithCancellation(cts.Token)
                      where s.Age < 25
                      select s.Age).Sum();
        }
        catch (OperationCanceledException ex)
        {
            Console.WriteLine(ex.Message);
        }
    }
).Start();
//外部動做觸發取消
cts.Cancel();

5、表達式樹

一、認識表達式目錄樹

出如今System.Linq.Expression中,就是爲Linq to sql服務的。表達式樹以樹形數據結構表示代碼,其中每個節點都是一種表達式。它能夠將咱們原來直接由代碼編寫的邏輯存儲在一個樹狀的結構裏,而後運行的時候就去動態解析這個樹。lambda表達式聲明表達式目錄樹能夠像下面這樣。

Func<intintint> func = (m, n) => m * n + 2;// new Func<int, int, int>((m, n) => m * n + 2);
Expression<Func<intintint>> exp = (m, n) => m * n + 2;//lambda表達式聲明表達式目錄樹
//Expression<Func<int, int, int>> exp1 = (m, n) =>//只能一行 不能有大括號
//    {
//        return m * n + 2;
//    };
//Queryable    //a=>a.Id>3

//表達式目錄樹:語法樹,或者說是一種數據結構;能夠被咱們解析
int iResult1 = func.Invoke(1223);
int iResult2 = exp.Compile().Invoke(1223);

二、拼裝表達式樹

若是使用Expression類接口聲明看起來會像下面這樣,注意比較Lambda表達式聲明和Expression類本身拼裝聲明的區別,最後都是須要Compile()編譯後執行。

Expression<Func<intintint>> exp = (m, n) => m * n + 2;
ParameterExpression parameterExpression = Expression.Parameter(typeof(int), "m");
ParameterExpression parameterExpression2 = Expression.Parameter(typeof(int), "n");
var multiply = Expression.Multiply(parameterExpression, parameterExpression2);
var constant = Expression.Constant(2typeof(int));
var add = Expression.Add(multiply, constant);

Expression<Func<intintint>> expression =
    Expression.Lambda<Func<intintint>>(
        add,
        new ParameterExpression[]
        {
             parameterExpression,
             parameterExpression2
        });

int iResult1 = exp.Compile().Invoke(1112);
int iResult2 = expression.Compile().Invoke(1112);

表達式樹是由表達式的主體body、表達式的參數parameters、表達式類型Type、返回類型NodeType組成。一個樹可能有不少葉子節點,複雜一點的例子像下面這樣

//i*j+w*x
ParameterExpression a = Expression.Parameter(typeof(int), "i");   //建立一個表達式樹中的參數,做爲一個節點,這裏是最下層的節點
ParameterExpression b = Expression.Parameter(typeof(int), "j");
BinaryExpression r1 = Expression.Multiply(a, b);    //這裏i*j,生成表達式樹中的一個節點,比上面節點高一級

ParameterExpression c = Expression.Parameter(typeof(int), "w");
ParameterExpression d = Expression.Parameter(typeof(int), "x");
BinaryExpression r2 = Expression.Multiply(c, d);

BinaryExpression result = Expression.Add(r1, r2);   //運算兩個中級節點,產生終結點

Expression<Func<intintintintint>> lambda = Expression.Lambda<Func<intintintintint>>(result, a, b, c, d);

Console.WriteLine(lambda + "");   //輸出‘(i,j,w,x)=>((i*j)+(w*x))’,z對應參數b,p對應參數a

Func<intintintintint> f = lambda.Compile();  //將表達式樹描述的lambda表達式,編譯爲可執行代碼,並生成該lambda表達式的委託;

Console.WriteLine(f(1111) + "");  //輸出結果2

上面例子造成的表達式樹就像下面這樣。

三、應用

最經常使用的地方仍是查詢數據時。以往咱們作一個查詢,根據用戶輸入,去數據庫中查詢匹配的信息,可能去想到去拼一條帶where條件的sql語句,而後去執行這條sql便可。
若是沒法肯定須要查詢的字段,當每換一個查詢條件或組合多個查詢條件,咱們能夠用表達式目錄樹動態的拼裝起來。

還能夠用來代替反射,咱們知道反射有性能問題,硬編碼是最快的,但不夠靈活。像泛型同樣,表達式樹能夠動態生成硬編碼,緩存後之後訪問調用就至關於硬編碼性能。好比示例中,咱們若是須要對一個類型對象轉換成另外一個對象。這裏能夠有不少方法,硬編碼、反射、序列化等均可以實現,如今咱們用表達式樹試一下。

People people = new People()
{
    Id = 11,
    Name = "Wang",
    Age = 31
};
PeopleCopy peopleCopy = new PeopleCopy()
{
    Id = people.Id,
    Name = people.Name,
    Age = people.Age
};

硬編碼像上面這樣,咱們用表達式目錄樹就是爲了可以生成這種硬編碼的委託

public class ExpressionMapper
{
    private static Dictionary<stringobject> _Dic = new Dictionary<stringobject>();

    /// <summary>
    /// 字典緩存表達式樹
    /// </summary>
    /// <typeparam name="TIn"></typeparam>
    /// <typeparam name="TOut"></typeparam>
    /// <param name="tIn"></param>
    /// <returns></returns>
    public static TOut Trans<TIn, TOut>(TIn tIn)
    {
        string key = string.Format("funckey_{0}_{1}"typeof(TIn).FullName, typeof(TOut).FullName);
        if (!_Dic.ContainsKey(key))
        {
            ParameterExpression parameterExpression = Expression.Parameter(typeof(TIn), "p");
            List<MemberBinding> memberBindingList = new List<MemberBinding>();
            foreach (var item in typeof(TOut).GetProperties())
            
{
                MemberExpression property = Expression.Property(parameterExpression, typeof(TIn).GetProperty(item.Name));
                MemberBinding memberBinding = Expression.Bind(item, property);
                memberBindingList.Add(memberBinding);
            }
            foreach (var item in typeof(TOut).GetFields())
            
{
                MemberExpression property = Expression.Field(parameterExpression, typeof(TIn).GetField(item.Name));
                MemberBinding memberBinding = Expression.Bind(item, property);
                memberBindingList.Add(memberBinding);
            }
            MemberInitExpression memberInitExpression = Expression.MemberInit(Expression.New(typeof(TOut)), memberBindingList.ToArray());
            Expression<Func<TIn, TOut>> lambda = Expression.Lambda<Func<TIn, TOut>>(memberInitExpression, new ParameterExpression[]
            {
                parameterExpression
            });
            Func<TIn, TOut> func = lambda.Compile();//拼裝是一次性的
            _Dic[key] = func;
        }
        return ((Func<TIn, TOut>)_Dic[key]).Invoke(tIn);
    }
}
var result = ExpressionMapper.Trans<People, PeopleCopy>(people);

表達式和表達式樹什麼關係呢?首先表達式是匿名方法生成委託實例,而表達式樹是一種數據結構,自己不能執行的,須要編譯成sql,而後解釋執行表達式樹中每一個節點的表達式。

6、小結

本章認識了Lambda表達式,linq查詢以及相關的經常使用操做符,它們不只用於篩選數據源,給數據源排序,還用於執行分區,分組,轉換,鏈接等操做,使用並行linq能夠提升大型數據集的查詢效率。另外一個重要概念就是表達式目錄樹。表達式目錄樹容許在運行期間構建對數據源的查詢,存儲在程序集中,主要用在linq to sql中,後面學習EntityFramework框架時會用到大量的表達式目錄樹。

 

 

debug everything

願一覺醒來,陽光正好

而不是,一覺醒來,天都黑了

 

若是手機在手邊,也能夠關注下vx:xishaobb,互動或獲取更多消息。固然這裏也一直更新de,下期見,拜了個拜拜。

相關文章
相關標籤/搜索