要想學好linq to object 我們必須要先學習lambda 表達式,學習lambda 表達式呢我們必須了解匿名函數和匿名類及擴展方法,學習匿名函數,我們必須學會委托,這是本文的宗旨。下面開始第一步。在第一步開始之前,我們做點准備工作,建立一個學生類和一個班級類,類結構如下
public class Student { public int Id { get; set; } public int ClassId { get; set; } public string Name { get; set; } public int Age { get; set; } public void Study() { Console.WriteLine("{0} {1}跟着Eleven老師學習.net高級開發", this.Id, this.Name); } } public class Class { public int Id { get; set; } public string ClassName { get; set; } }
在准備一個基礎數據類
public class LinqShow { /// <summary> /// 准備數據 /// </summary> /// <returns></returns> public List<Student> GetStudentList() { #region 初始化數據 List<Student> studentList = new List<Student>() { new Student() { Id=1, Name="打兔子的獵人", ClassId=2, Age=35 }, new Student() { Id=1, Name="Alpha Go", ClassId=2, Age=23 }, new Student() { Id=1, Name="白開水", ClassId=2, Age=27 }, new Student() { Id=1, Name="狼牙道", ClassId=2, Age=26 }, new Student() { Id=1, Name="Nine", ClassId=2, Age=25 }, new Student() { Id=1, Name="Y", ClassId=2, Age=24 }, new Student() { Id=1, Name="小昶", ClassId=2, Age=21 }, new Student() { Id=1, Name="yoyo", ClassId=2, Age=22 }, new Student() { Id=1, Name="冰亮", ClassId=2, Age=34 }, new Student() { Id=1, Name="瀚", ClassId=2, Age=30 }, new Student() { Id=1, Name="畢帆", ClassId=2, Age=30 }, new Student() { Id=1, Name="一點半", ClassId=2, Age=30 }, new Student() { Id=1, Name="小石頭", ClassId=2, Age=28 }, new Student() { Id=1, Name="大海", ClassId=2, Age=30 }, new Student() { Id=3, Name="yoyo", ClassId=3, Age=30 }, new Student() { Id=4, Name="unknown", ClassId=4, Age=30 } }; #endregion return studentList; } }
簡單了解委托
1. 委托是什么?
其實,我一直思考如何講解委托,才能把委托說得更透徹。說實話,每個人都委托都有不同的見解,因為看問題的角度不同。個人認為,可以從以下2點來理解:
(1) 從數據結構來講,委托是和類一樣是一種用戶自定義類型。
(2) 從設計模式來講,委托(類)提供了方法(對象)的抽象。
既然委托是一種類型,那么它存儲的是什么數據?
我們知道,委托是方法的抽象,它存儲的就是一系列具有相同簽名和返回回類型的方法的地址。調用委托的時候,委托包含的所有方法將被執行。
2. 委托類型的定義
委托是類型,就好像類是類型一樣。與類一樣,委托類型必須在被用來創建變量以及類型對象之前聲明。
delegate void MyDel(int x);
委托類型聲明:
(1) 以deleagate關鍵字開頭。
(2)返回類型+委托類型名+參數列表。
3. 聲明委托變量
MyDel del1,del2;
4. 初始化委托變量
(1) 使用new運算符
new運算符的操作數的組成如下:
- 委托類型名
- 一組圓括號,其中包含作為調用列表中的第一個成員的方法的名字。方法可以是實例方法或靜態方法。
del1 = new MyDel( 實例方法名 ); del2 = new MyDel( 靜態方法名 );
(2)使用快捷語法
快鍵語法,它僅由方法說明符構成。之所以能這樣,是因為在方法名稱和其相應的委托類型之間有隱式轉換。
del1 = 實例方法名; del2 = 靜態方法名;
5. 賦值委托
由於委托是引用類型,我們可以通過給它賦值來改變包含在委托變量中的方法地址引用。舊的引用會被垃圾回收器回收。
MyDel del; del = 實例方法名; //委托初始化 del = 靜態方法名;//委托重新賦值,舊的引用將被回收
6. 委托調用
委托調用跟方法調用類似。委托調用后,調用列表的每個方法將會被執行。
在調用委托前,應判斷委托是否為空。調用空委托會拋出異常,這是正常標准調用
if(null != del) { del.Invoke();//委托調用 }
在net 1.1的時代,我們是使用Invoke()方法類調用委托的。但是微軟在net 2.0 的時候,我們可以這么去調用委托 直接 del();
if(null != del) { del();//委托調用 }
7. 綜合練習
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace Test { class Program { public delegate void meathed1();//1 委托的聲明 委托是一種類型 public delegate int meathed2();//2 可以聲明在類的內部,也可以聲明在類的外部 public delegate void meathed3(int id, string name); public delegate string meathed4(string msg); static void Main(string[] args) { meathed1 me1 = new meathed1(m1); meathed2 me2 = new meathed2(m2); meathed3 me3 = new meathed3(m3); meathed4 me4 = new meathed4(m4); me1.Invoke(); int result= me2.Invoke(); Console.WriteLine(result); me3(1, "wbc"); Console.WriteLine(me4("您吃了嗎")); Console.Read(); } public static void m1() { Console.WriteLine("我是無參數無返回值的方法"); } public static int m2() { Console.WriteLine("我是無參數有返回值的方法~~~~~~~~~"); return 123; } public static void m3(int id, string name) { Console.WriteLine($"我是有參數無返回值的方法,id={id},name={name}"); } public static string m4(string sparams) { Console.WriteLine($"我是無參數無返回值的方法 ,參數{sparams}"); return "參數返回了"; } } }
8. 多播委托
多播委托(MulticastDelegate)繼承自 Delegate ,表示多路廣播委托;即,其調用列表中可以擁有多個元素的委托。實際上,我們自定義的委托的基類就是 MulticastDelegate。這句話不好理解,繼續往下看
當我們自己寫一個類去繼承Delegate 的時候,開發工具提示我 特殊類型不允許被繼承,估計這是net 唯一一個特殊類型。
public abstract class myted : Delegate { }
我們來看一個案列來使用下多播委托
meathed1 me1 = new meathed1(m1); me1 += m2; me1 += m3; me1.Invoke(); me1 -= m2; me1();
多播委托:
+= 的時候就是在委托的實例之后加入注冊更多的方法,像一個鏈子,執行的時候按加入的先后順序執行
-=的時候就是在委托的實例之后移除注冊更多的方法。從鏈子的尾部開始匹配,找到一個完全吻合的移除,沒有找到不會觸發異常。
注:實例方法因為每一次實例地址的引用不一致,所以無法移除。
9. net 框架提供的自帶委托
8.1.Func委托類型
Func是有返回值的泛型委托,可以沒有參數,但最多只有16個參數,並且必須要有返回值。
Func<string, int> funcDelegate = new Func<string, int>();
2.Action委托類型
Action是沒有返回值的泛型委托,可以沒有參數,但最多只有16個參數。
Action<string, string> method = new Action<string, string>();
關於泛型這里就不過多介紹了,本人前邊寫了一篇比較詳細。建議大家以后使用委托的時候,盡量使用net 類庫自帶的委托。
這里擴展下事件的基本使用,事件就是在委托的實例加上一個event 的關鍵字。所以委托是一種類型,事件就是委托類型的實例。這里只是擴展,不懂不影響學習linq to object

public void Meta() { } public delegate void meathed1(); public class Cat { public event meathed1 met_event; public meathed1 mea = new meathed1(m1); public void Run() { mea += m2; mea.Invoke(); met_event = new meathed1(m1); met_event += m2; met_event.Invoke(); } public static void m1() { } public static void m2() { } }
調用方法如下
Cat c = new Cat(); c.mea = Meta; c.mea.Invoke(); // c.met_event = meta;這種寫法是不允許的,只有通過訂閱的方式才允許,如下 c.met_event += new meathed1(Meta);
使用事件和委托的區別我們一下子就能區分出來:事件更安全,在類的外部不允許破壞他的原有執行內容,只有在從新訂閱 event關鍵字限制了權限,保證安全
匿名方法&lambda表達式
我們看了上面的委托,發現寫起來好麻煩,並且用起來特別的不方便,有沒有更簡便的方法呢?可不可以把我們的方法寫到委托里面呢?答案是肯定的;這種寫法我們稱為匿名方法。寫法如下:
Action<string> method = new Action<string>(delegate(string name){ Console.WriteLine(name); }); method.Invoke("wbc"); Console.Read();
但是這種寫法還不夠簡便,在.net 2.0的時候,我們發現1.1的時候在頻繁的使用委托,微軟做了很大的改進和更新,新的寫法如下
Action<string> method = new Action<string>(name => Console.WriteLine(name) ); method.Invoke("wbc"); Console.Read();
在2.0里面,我們去掉了delegate關鍵字,如果子有一個參數的時候,連括號也省略了,用"=>"指向方法體,這個尖叫號等號我們讀做“goes to”,如果方法只有一句話,連花括號也省略了。參數類型直接就可以推算出來。看一個復雜的案列
Func<string,int,string> method = new Func<string, int, string>((name,age) => { return $"{name}今年{age}歲了"; } ); string result= method.Invoke("wbc",0); Console.WriteLine(result); Console.Read();
其實我們的匿名方法就是lambda 表達式。
匿名類
有了匿名方法一定會有匿名類,在net 3.0的時候才有的匿名類,我們看下在沒有匿名類的時候,我們是如何去寫的。
object student = new { id=123, name="1234" }; Console.WriteLine(student); Console.Read();
這種寫法只能輸出 “{ id = 123, name = 1234 }”這種結果的Json 數據,並且我們在程序中不能使用student.id,會報錯。看下匿名寫法
var model = new { Id = 1, Name = "Alpha Go", Age = 21, ClassId = 2 }; Console.WriteLine(model.Age); Console.Read();
我們來看下var 關鍵字,其實var 關鍵字就是一個語法糖,我們不必過於在意,這里就不過多的演示了,我們的根本目的是linq to object
1. 必須在定義時初始化。也就是必須是var s = “abcd”形式,而不能是如下形式:
var s;
s = “abcd”;//這種寫法是不允許的
2. 一但初始化完成,就不能再給變量賦與初始化值類型不同的值了。
3. var要求是局部變量。
4. 使用var定義變量和object不同,它在效率上和使用強類型方式定義變量完全一樣。
這里在簡要擴展點技術點“dynamic”,dynamic的用法和object差不多,區別是dynamic在編譯的時候才聲明的一個類型,但是本人建議在程序開發中盡量避開這個關鍵字,后續MVC 中在詳細介紹,dynamic是net 40里面的關鍵字。
擴展方法
擴展方法必須建立在靜態類中的靜態方法,且第一個參數前邊要加上this 關鍵字,使用擴展方法必須引用擴展方法所在的類的命名空間,如果想要全局使用,可以不帶命名空間。個人建議程序中的擴展方法盡量要帶上命名空間。我們來看下擴展方法的使用
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace Test { class Program { static void Main(string[] args) { int? a = null; Console.WriteLine(Helper.ToInt(a));//常規寫法輸出 Console.WriteLine(a.ToInt());//擴展方法輸出 if ("1qazwsx1qazwsx1qazwsx1qazwsx".ToLengthInCount(16)) { Console.WriteLine("密碼長度應當小於16個字符"); } Console.Read(); } } public static class Helper { /// <summary> /// 驗證一個數字是否為空,為空返回-1 /// 擴展方法:靜態類里的靜態方法,第一個參數類型前面加上this /// </summary> /// <param name="iParameter"></param> /// <returns></returns> public static int ToInt(this int? iParameter) { return iParameter ?? -1; } /// <summary> /// 驗證一個字符串長度是否大指定長度 /// /// </summary> /// <param name="sParameter"></param> /// <returns></returns> public static bool ToLengthInCount(this string sParameter, int iCount) { return sParameter.Length >= iCount ? true : false; } } }
這里簡要說明下,擴展方法的方法名和類內不得方法名相同,會優先選擇類內部的方法,這是以就近原則,去執行的,關於就近原則,請看本人java 相關博客,有介紹
linq to object
1. 什么是linq to object ?
LINQ即Language Integrated Query(語言集成查詢),LINQ是集成到C#和Visual Basic.NET這些語言中用於提供查詢數據能力的一個新特性。linq to object 就是對象查詢體系。
注:LINQ(發音為Link)
我們來看一個簡單的列子,用到了我們前邊准備的兩個類。我們要取出學生年齡小於30歲的,我們如果不會linq to object 的寫法如下
LinqShow linq = new LinqShow(); List<Student> studentList = linq.GetStudentList();//獲取基礎數據 List<Student> studentListLessThan30 = new List<Student>(); foreach (var item in studentList) { if (item.Age < 30) { Console.WriteLine($"name={item.Name},age={item.Age}"); studentListLessThan30.Add(item); } } Console.Read();
我們可不可以使用委托去計算呢??
LinqShow linq = new LinqShow(); List<Student> studentList = linq.GetStudentList();//獲取基礎數據 List<Student> studentListLessThan30 = new List<Student>(); //Func<Student, bool> predicate = s => s.Age < 30;//寫法一, //var list = studentList.Where<Student>(predicate);//linq var list = Enumerable.Where(studentList, s => s.Age < 30);//使用lambda 表達式寫法 foreach (var item in list) { Console.WriteLine($"Name={item.Name} Age={item.Age}"); } Console.Read();
兩段代碼我就不比較了,這里我們只來剖析下 studentList 的Where方法,我們可以轉移到定義看下,其實where 里面的參數,如下圖
我們會發現,這不List<T>自帶的一個方法,而是一個擴展方法,這個擴展方法在”System.Linq“命名空間的“public static class Enumerable”類下,也就是說根據擴展方法的特點,我們只要引用了“System.Linq”命名空間,就能使用這個擴展方法,這個擴展方法真的第二個參數是一個Func<TSource, bool> 委托,這個委托有一個TSource泛型參數,一個bool返回值。這就說明了我們上面的lambda 表達式是成立的。
繼續剖析,我們接受的時候,沒有使用List<Student>接收返回的集合,這是因為我們的linq 只有遇到循環的時候或遇到ToList<T>()的時候才會去迭代,這里涉及到迭代器的問題,后續我們會詳細講解迭代器的使用,或者大家去網上查詢下設計模式迭代器。這么說,我們很難理解。我們通過一個案列來分析,自己寫一個for 循環的擴展方法,對比下
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace Test { class Program { static void Main(string[] args) { LinqShow linq = new LinqShow(); List<Student> studentList = linq.GetStudentList();//獲取基礎數據 //var list = Enumerable.Where(studentList, s => s.Age < 30);//使用lambda 表達式寫法, Console.WriteLine($"-------------------------linq寫法-----------------------------------"); var list = studentList.Where( s => { Console.WriteLine($"我的年齡是{s.Age}"); return s.Age < 30; });//使用lambda 表達式寫法,這里在每一次計算,我們都輸出一個我的年齡是,這里不再使用 Enumerable 去調用where ,反正是擴展方法,用法是一樣的 foreach (var item in list) { Console.WriteLine($"Name={item.Name} Age={item.Age}"); } Console.WriteLine($"-------------------------自定義擴展方法寫法-----------------------------------"); var Wbclist = studentList.WbcWhere(s => { Console.WriteLine($"Wbc我的年齡是{s.Age}"); return s.Age < 30; });// 自定義擴展方法寫法 這里不再使用 Enumerable 去調用where foreach (var item in Wbclist) { Console.WriteLine($"Name={item.Name} Age={item.Age}"); } Console.Read(); } } public static class Helper { /// <summary> /// 自定義擴展方法計算集合 /// </summary> /// <typeparam name="TSource"></typeparam> /// <param name="source"></param> /// <param name="predicate"></param> /// <returns></returns> public static IEnumerable<TSource> WbcWhere<TSource>(this IEnumerable<TSource> source, Func<TSource, bool> predicate) { //常規情況下數據過濾 List<TSource> studentListLessThan30 = new List<TSource>(); foreach (var student in source) { //if (item.Age < 30) if (predicate(student)) { studentListLessThan30.Add(student); } } return studentListLessThan30; } /// <summary> /// 驗證一個數字是否為空,為空返回-1 /// 擴展方法:靜態類里的靜態方法,第一個參數類型前面加上this /// </summary> /// <param name="iParameter"></param> /// <returns></returns> public static int ToInt(this int? iParameter) { return iParameter ?? -1; } /// <summary> /// 驗證一個字符串長度是否大指定長度 /// /// </summary> /// <param name="sParameter"></param> /// <returns></returns> public static bool ToLengthInCount(this string sParameter, int iCount) { return sParameter.Length >= iCount ? true : false; } } }
我們來看下輸入結果
通過執行結果,我們會發現,自定義的擴展方法是循環一遍,統一輸出的,沒有延遲,而linq 的寫法有延遲。這也就是我們linq 核心之一 “linq 是有延遲的”。這里特別說明,linq 的延遲加載和EF ,ORM 的延遲加載,不是一會事,這里說的延遲,指的是當用到某條數據的時候,才會去查詢。
2. 高逼格裝X時刻
我們以llambda表達式去寫linq 稱為 查詢運算符寫法,下邊我們 使用 查詢表達式去寫,注意區分什么是查詢表達式,什么是查詢運算符寫法,我面試的很多人都說反了。
案列一 多條件過濾篩選數據
LinqShow linq = new LinqShow(); List<Student> studentList = linq.GetStudentList();//獲取基礎數據 //查詢運算符 var list1 = studentList.Where<Student>(s => s.Age < 30 && s.Name.Length > 3);//陳述句 foreach (var item in list1) { Console.WriteLine("Name={0} Age={1}", item.Name, item.Age); } //查詢表達式 Console.WriteLine("********************"); var list2 = from s in studentList where s.Age < 30 && s.Name.Length > 3 select s; foreach (var item in list2) { Console.WriteLine("Name={0} Age={1}", item.Name, item.Age); } Console.Read();
我們會發現,表達式的寫法是不是很像sql ,語法以from 對象 ... in .. where select 對象 ,是不是很高大上,夠逼咯吧!!!!!!,下邊在簡單介紹幾個案列
案列二將篩選數據付給匿名類或者實體類
{ Console.WriteLine("********************"); var list = studentList.Where<Student>(s => s.Age < 30) .Select(s => new { IdName = s.Id + s.Name, ClassName = s.ClassId == 2 ? "高級班" : "其他班" }); foreach (var item in list) { Console.WriteLine("Name={0} Age={1}", item.ClassName, item.IdName); } } { Console.WriteLine("********************"); var list = from s in studentList where s.Age < 30 select new { IdName = s.Id + s.Name, ClassName = s.ClassId == 2 ? "高級班" : "其他班" }; foreach (var item in list) { Console.WriteLine("Name={0} Age={1}", item.ClassName, item.IdName); } }
案列三 排序分頁
var list = studentList.Where<Student>(s => s.Age < 30) .Select(s => new { Id = s.Id, ClassId = s.ClassId, IdName = s.Id + s.Name, ClassName = s.ClassId == 2 ? "高級班" : "其他班" }) .OrderBy(s => s.Id)//首先升序排序 .OrderByDescending(s => s.ClassId)//首先降序排序 .ThenBy(s => s.ClassId)//升序降序排序以后再排序 .Skip(2)//跳過 分頁 .Take(3)//獲取數據 ; foreach (var item in list) { Console.WriteLine($"Name={item.ClassName} Age={item.IdName}"); }
案列四 關聯查詢
首先添加班級數據

List<Class> classList = new List<Class>() { new Class() { Id=1, ClassName="初級班" }, new Class() { Id=2, ClassName="高級班" }, new Class() { Id=3, 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}"); }
算數運輸符寫法
var list = studentList.Join(classList, s => s.ClassId, c => c.Id, (s, c) => new { Name = s.Name, CalssName = c.ClassName }); foreach (var item in list) { Console.WriteLine($"Name={item.Name},CalssName={item.CalssName}"); }
在linq 中沒有右鏈接,如果要寫右鏈接,只能反過來寫,這里不過多去寫案列了,這種案列網上很多,一會總結過后,分享幾篇
總結及推薦
1.匿名函數只能使用在委托中
2.var 關鍵字的特點,如何使用var 實現匿名類。
3. lambda 表達式
4.擴展方法
5.運算符linq 寫法
6.表達式linq 寫法
7 linq 的延遲加載原理
分享 1 linq 案列 ,很全面的
分享2 linq 標准查詢操作符 很全面的 共五篇