Linq 可以輕松的查詢對象集合重付。Linq代表語言集成查詢填硕,是.NET框架的擴展柜裸,支持從數據庫擂错、程序對象的集合以及XML文檔中查詢數據
一個簡單的示例:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace CodeForLinq
{
class Program
{
static void Main(string[] args)
{
//創(chuàng)建一個int數組,作為被查詢的數據源
int[] numbers = { 1, 2, 3, 4, 5, 18 };
//Linq定義查詢,注意硅蹦,這里只是“定義”而已
IEnumerable<int> lowNum =
from nu in numbers
where nu < 10
select nu;
//遍歷lowNum怜浅,只有使用lowNum的時候,數據才會被查詢出來
//所以爽醋,在這里才被執(zhí)行了查詢
foreach (int item in lowNum)
{
Console.WriteLine(item);
}
Console.ReadKey();
}
}
}
針對于不同的數據源攒暇,需要實現相應的Linq查詢的代碼模塊,這些代碼模塊被稱作Linq提供程序子房。在C#中形用,覺的Linq提供程序有Linq to Object/ Linq to XML/ BLinq(Asp.Net)等
匿名類
在深入了解Linq之前,需要先了解一下匿名類证杭,因為在使用Linq語句的時候田度,會大量的使用匿名類
示例:使用匿名類型創(chuàng)建一個學生類
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace CodeForLinq
{
class Program
{
static void Main(string[] args)
{
//創(chuàng)建一個學生類
var student = new { Name = "Jack", Age = 18, Class = "013" };
Console.ReadKey();
}
}
}
解析:
創(chuàng)建一個匿名類需要用到關鍵字new,然后直接在后面跟{ }來初始化類中成員(屬性)解愤,多個成員之間使用逗號分隔镇饺;因為沒有指定類型,所有在接收創(chuàng)建的對象時送讲,需要用到關鍵字var奸笤,而且必須使用var關鍵字
- 匿名類型只能和局部變量配合使用,不能用于類成員
- 由于匿名類沒有名字哼鬓,所以必須以var關鍵字作為變量類型
- 不能設置匿名類型對象的屬性监右,因為匿名類的成員是只讀的
在初始化一個匿名類型對象時,其成員的初始化不僅可以使用賦值操作异希,還可以使用成員訪問表達式和標識符形式
示例:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace CodeForLinq
{
class Other
{
public static string Name = "Bob";
}
class Program
{
static void Main(string[] args)
{
//局部變量健盒,表示班級
string Class = "013";
//創(chuàng)建一個學生類
var student = new { Other.Name, Age = 18, Class };
//訪問學生類中成員
Console.WriteLine("My name is "+student.Name);
Console.WriteLine("I'm "+student.Age+" years old");
Console.WriteLine("My Class is " + student.Class);
Console.ReadKey();
}
}
}
var student = new { Other.Name, Age = 18, Class };
的效果等同于var student = new { Name = Other.Name, Age = 18, Class = Class};
如果再聲明一個具有相同的參數名、相同的推斷類型和相同順序的匿名類型的話,編譯器會重用這個類型直接創(chuàng)建新的實例扣癣,而不會創(chuàng)建新的匿名類型
方法語法和查詢語法
查詢語法:看上去和SQL語句很相似惰帽,使用查詢表達形式書寫
方法語法:使用標準的方法調用
查詢語法是聲明式的,但未指明如何執(zhí)行這個查詢父虑;方法語法是命令式的该酗,它指明了方法查詢調用的順序
編譯器會將使用語法表示的查詢翻譯為方法調用的形式,在運行時這兩種方式沒有性能上的差異
先看方法語法與查詢語法的示例:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace CodeForLinq
{
class Other
{
public static string Name = "Bob";
}
class Program
{
static void Main(string[] args)
{
//創(chuàng)建一個int數組士嚎,作為被查詢的數據源
int[] numbers = { 1, 2, 3, 4, 5, 18 };
//查詢語法
var _numbers =
from nu in numbers
where nu < 10
select nu;
//方法語法,Where方法的參數使用了Lambda表達式
var _num = numbers.Where(x => x < 10);
foreach (int item in _numbers)
{
Console.WriteLine(item);
}
foreach (int item in _num)
{
Console.WriteLine(item);
}
Console.ReadKey();
}
}
}
查詢變量
Linq查詢返回的結果可以是一個枚舉垂涯,也可以是一個叫做標量的單一值
示例:
//創(chuàng)建一個int數組,作為被查詢的數據源
int[] numbers = { 1, 2, 3, 4, 5, 18 };
//返回一個IEnumerable結果航邢,它可以枚舉返回的結果
IEnumerable<int> _numbers =
from nu in numbers
where nu < 10
select nu;
//通過Count()方法返回查詢結果總數量
int _count =
(from nu in numbers
where nu < 10
select nu).Count();
等號左邊的變量叫做查詢變量耕赘,這里指_numbers
和_count
查詢變量一般使用var類型來讓編譯器自動推斷其返回的類型
如果查詢語句返回的是枚舉類型,查詢變量中是不會包含結果的膳殷,只有在真正使用枚舉值的時候才會執(zhí)行查詢操骡,并且每次使用枚舉值的時候都會執(zhí)行一次查詢語句;而如果查詢語句返回的是標題赚窃,查詢則立即生效册招,并把結果保存在查詢變量中
查詢表達式的結構
from子名指定數據源,并且引入迭代變量勒极;迭代變量逐個表示數據源的每一個元素是掰;語法如下:
from [Type] item in Items
Items表示數據源;item表示數據源中的元素辱匿;Type是可選的键痛,表示元素的類型
join子句,聯結語句可以結合兩個或多個集合中的數據匾七,然后產生一個臨時的對象集合絮短,每個集合中都包含原始集合對象中的所有元素,語法如下:
join Identifier in Collection2 on Field1 equqls Field2
示例:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace CodeForLinq
{
class Course//課程類
{
public int ID;
public int Student_ID;
public string Course_Name;
}
class Student//學生類
{
public int ID;
public string Name;
}
class Program
{
static void Main(string[] args)
{
//學生類集合
Student[] st = new Student[] {
new Student{ID=111,Name="Bob"},
new Student{ID=112,Name="Jack"},
new Student{ID=113,Name="Hong"}
};
//課程類集合
Course[] co = new Course[] {
new Course{ID=1, Student_ID=111,Course_Name="數學"},
new Course{ID=2, Student_ID=112,Course_Name="語文"},
new Course{ID=3, Student_ID=113,Course_Name="化學"},
new Course{ID=4, Student_ID=112,Course_Name="數學"},
new Course{ID=5, Student_ID=112,Course_Name="生物"}
};
//Linq查詢語法
var result =
from a in st //指定第一個數據源st
join b in co on a.ID equals b.Student_ID //聯結第二個數據源ot昨忆,并用on指定聯結條件丁频,equals來指定比較字段
where b.Course_Name=="數學" //匹配數學課程
select a.Name; //返回名字
foreach (var name in result)
{
Console.WriteLine("參加數學課程的學生名:"+name);
}
Console.ReadKey();
}
}
}
from...let...where片段
可以使用多個from子句指定多個數據源,示例:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace CodeForLinq
{
class Course//課程類
{
public int ID;
public int Student_ID;
public string Course_Name;
}
class Student//學生類
{
public int ID;
public string Name;
}
class Program
{
static void Main(string[] args)
{
//學生類集合
Student[] st = new Student[] {
new Student{ID=111,Name="Bob"},
new Student{ID=112,Name="Jack"},
new Student{ID=113,Name="Hong"}
};
//課程類集合
Course[] co = new Course[] {
new Course{ID=1, Student_ID=111,Course_Name="數學"},
new Course{ID=2, Student_ID=112,Course_Name="語文"},
new Course{ID=3, Student_ID=113,Course_Name="化學"},
new Course{ID=4, Student_ID=112,Course_Name="數學"},
new Course{ID=5, Student_ID=112,Course_Name="生物"}
};
//指定多個數據源
var st_co =
from a in st
from b in co
where a.ID == b.Student_ID && b.Course_Name=="數學"
select new { a.Name, b.Course_Name };//創(chuàng)建一個匿名類型對象
//訪問返回集體中的成員
foreach (var item in st_co)
{
Console.WriteLine("學生:"+item.Name);
Console.WriteLine("課程:"+item.Course_Name);
}
Console.ReadKey();
}
}
}
let子句接受一個表達式的運算邑贴,并且把它賦值給一個需要在其它地方運算中使用的標識符
示例:
//定義兩個數據源
int[] number1 = { 1, 2, 3, 4, 5 };
int[] numbers2 = { 1, 2, 3, 4, 5, 18 };
var nu_array =
from a in number1
from b in numbers2
let sum = a + b //使用let子句將第一個集合中的元素與第二個集合中的元素進行相加
where sum == 4
select new { a, b, sum };
foreach (var item in nu_array)
{
Console.WriteLine(item.a + "," + item.b + "," + item.sum);
}
where子句根據之后運算來去除不符合指定條件的項席里,在from...let...where片段中可以有任意多個where子句
示例:
//定義兩個數據源
int[] number1 = { 1, 2, 3, 4, 5 };
int[] numbers2 = { 1, 2, 3, 4, 5, 18 };
var nu_array =
from a in number1
from b in numbers2
let sum = a + b //使用let子句將第一個集合中的元素與第二個集合中的元素進行相加
where sum == 4 //篩選a+b等于4的所有元素
where a == 2 //再指定a必須等于2,那返回的結果中拢驾,b就只能是等于2了
select new { a, b, sum };
foreach (var item in nu_array)
{
Console.WriteLine(item.a + "," + item.b + "," + item.sum);
}
orderby子句
orderby子句接受一個表達式奖磁,并根據表達式按順序返回結果;排列的表達式也可以是集合中的成員
- orderby子句默認是按升序排列的独旷;可以使用ascending顯示的指定為升序或使用descending指定為隆序
- 可以有任意多個子句署穗,之間使用逗號分隔
示例:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace CodeForLinq
{
class Course//課程類
{
public int ID;
public int Student_ID;
public string Course_Name;
}
class Student//學生類
{
public int ID;
public string Name;
public int Age;
}
class Program
{
static void Main(string[] args)
{
//學生類集合
Student[] st = new Student[] {
new Student{ID=111,Name="Bob",Age=12},
new Student{ID=112,Name="Jack",Age=15},
new Student{ID=113,Name="Hong",Age=9}
};
//課程類集合
Course[] co = new Course[] {
new Course{ID=1, Student_ID=111,Course_Name="數學"},
new Course{ID=2, Student_ID=112,Course_Name="語文"},
new Course{ID=3, Student_ID=113,Course_Name="化學"},
new Course{ID=4, Student_ID=112,Course_Name="數學"},
new Course{ID=5, Student_ID=112,Course_Name="生物"}
};
var query = from student in st
orderby student.Age // 根據Age字段進行排序
select student;
foreach (var item in query)
{
Console.WriteLine(string.Format("ID:{0},名字:{1},年齡:{2}",item.ID,item.Name,item.Age));
}
Console.ReadKey();
}
}
}
select...group子句
select子句 指定所選對象的哪部分應該被選擇寥裂;指定的部分可以是整個數據項嵌洼,或數據項的一個字段案疲,或數據項的幾個字段組成的新的對象
group by子句 是可選的,用來指定選擇的項如何分組
select子句示例:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace CodeForLinq
{
class Student//學生類
{
public int ID;
public string Name;
public int Age;
}
class Program
{
static void Main(string[] args)
{
//學生類集合
Student[] st = new Student[] {
new Student{ID=111,Name="Bob",Age=12},
new Student{ID=112,Name="Jack",Age=15},
new Student{ID=113,Name="Hong",Age=9}
};
var query = from student in st
//select student.Name //選擇一個字段
//select new {student.Name, student.Age} //選擇多個字段組成的新對象
select student;// 選擇所有的sutdent元素
foreach (var item in query)
{
Console.WriteLine(string.Format("ID:{0},名字:{1},年齡:{2}",item.ID,item.Name,item.Age));
}
Console.ReadKey();
}
}
}
查詢中的匿名類——查詢結果可以由原始集合的項麻养、項的某些字段或匿名類型組成褐啡,例如select new {student.Name, student.Age}
group子句將select的對象根據一些標準進行分組
- 如果項包含在查詢語句中,它就可以根據某個字段的值進行分組鳖昌;作為分組的依據的屬性叫做健(key)
- gorup將返回可以枚舉已經形成的項的分組的可枚舉類型
- 分組本身是可被枚舉的
示例:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace CodeForLinq
{
class Student//學生類
{
public int ID;
public string Name;
public int Age;
}
class Program
{
static void Main(string[] args)
{
//學生類集合
Student[] st = new Student[] {
new Student{ID=111,Name="Bob",Age=12},
new Student{ID=112,Name="Jack",Age=15},
new Student{ID=113,Name="Json",Age=15},
new Student{ID=114,Name="Hong",Age=9}
};
var query =
from student in st
group student by student.Age; //按照年齡來分組
foreach (var item in query)
{
Console.WriteLine("年齡組:"+item.Key); //通過Key來找到分組的依據
foreach (var it in item)
{
Console.WriteLine("\t名字:"+it.Name);
}
}
Console.ReadKey();
}
}
}
查詢延續(xù):into子句
查詢延續(xù)子句可以接受查詢的一部分結果并賦予一個名字备畦,從而可以在查詢的另一部分中使用
示例:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace CodeForLinq
{
class Course//課程類
{
public int ID;
public int Student_ID;
public string Course_Name;
}
class Student//學生類
{
public int ID;
public string Name;
public int Age;
}
class Program
{
static void Main(string[] args)
{
int[] number1 = { 1, 2, 3, 4, 5 };
int[] numbers2 = { 1, 2, 3, 4, 5, 18 };
var result =
from a in number1
join b in numbers2 on a equals b
into a_b //通過into將number1與numbers2聯合命名為a_b
from c in a_b
select c;
foreach (var item in result)
{
Console.WriteLine(item);
}
Console.ReadKey();
}
}
}
標準查詢運算符
- 被查詢的對象叫做序列,它必須實現IEnumberable<T>接口
- 標準查詢運算符使用方法語法
- 一些運算符返回IEnumberable對象许昨,而其他的一些 運算符返回標量
- 很多操作都可以一個Lambda表達式做為參數
示例:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace CodeForLinq
{
class Course//課程類
{
public int ID;
public int Student_ID;
public string Course_Name;
}
class Student//學生類
{
public int ID;
public string Name;
public int Age;
}
class Program
{
static void Main(string[] args)
{
int[] number = { 1, 2, 3, 4, 5 };
//total與hwoMay都是標量
//被操作的對象number是一個序列
//而Sum()與Count()是去處符(方法)
int total = number.Sum();
int howMany = number.Count();
Console.ReadKey();
}
}
}
序列是指實現了IEnumberable<T>接口的類懂盐,包括List<>/Dictionary<>/Stack<>/Array等待
共有47個標題運算符,下面列舉幾個常用的運算符:
- Where -- 根據給定的條件對序列進行過濾
- Select -- 指定要包含一個對象或
- Join -- 對兩個序列執(zhí)行內聯結
- GroupBy -- 分組序列中的元素
- Dinstinct -- 去除序列中的重復項
- ToList -- 將序列作為List<T>返回
- First -- 返回序列中第一個與條件相匹配的元素
- FirstOrDefault -- 返回序列中第一個與條件相匹配的元素糕档,如果匹配不到莉恼,就返回第一個元素
- Last -- 返回序列中最后一個與條件相匹配的元素
- LastOrDefault -- 返回序列中最后一個與條件相匹配的元素,如果匹配不到速那,就返回最后一個元素
- Count -- 返回序列中元素的個數
- Sum -- 返回序列中值的總和
- Min -- 返回序列中值的最小值
- Max -- 返回序列中值的最大值
- Average -- 返回序列中值的平均值
- Contains -- 返回一個布爾值俐银,指明序列中是否包含某個元素
標準查詢運算符的簽名
System.Linq.Enumberable類聲明了標準查詢運算符方法,它們都擴展了IEnumberable<T>泛型類的擴展方法
擴展方法是公共靜態(tài)的端仰,盡管定義在一個類中捶惜,但目的是為另一個類(第一個形參)增加功能。該參數前必須有關鍵字this
- 由于運算符是泛型方法荔烧, 因此每個方法名都具有相關泛型參數(T)
- 由于運算符是擴展IEnumberable的擴展方法吱七,它們必須滿足以下的語法條件
- 聲明為Public和Static
- 在第一個參數前有this指示器
- 把IEnumberable<T>作為第一個參數類型
下面來看Count、Where鹤竭、First三個方法
//Count
public static int Count<TSource>(this IEnumerable<TSource> source);
//Where
public static IEnumerable<TSource> Where<TSource>(this IEnumerable<TSource> source, Func<TSource, bool> predicate);
//First
public static TSource First<TSource>(this IEnumerable<TSource> source);
它們都是Public和Static的
它們都是一個泛型方法
形參里的this關鍵字指出了它們量種擴展方法陪捷,this后面的泛型類是被擴展的類,在這里可以看出诺擅,它們都擴展了
IEnumerable<T>
類
如果關于擴展方法的概念是不是很清楚市袖,請參考泛型,敝人本篇文章有關于擴展方法的詳細講解
示例:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace CodeForLinq
{
class Program
{
static void Main(string[] args)
{
//數組是IEnumerable<T>的派生類
int[] number = new int[] { 1, 2, 3, 4, 5 };
//方法語法烁涌,數組作為參數
//可以直接通過Enumerable靜態(tài)類來訪問相關方法
var _count = Enumerable.Count(number);
var _first = Enumerable.First(number);
//擴展語法苍碟,數組被做為被擴展的對象
//Enumerable擴展了IEnumerable<T>,所以可以在被擴展的類里調用擴展方法
var __count = number.Count();
var __first = number.First();
Console.ReadKey();
}
}
}
查詢表達式與標準查詢運算符結合使用
每一個查詢表達式都會被編譯器翻譯成標準查詢運算符的形式,兩者可以結合使用撮执,示例:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace CodeForLinq
{
class Program
{
static void Main(string[] args)
{
int[] number = new int[] { 1, 2, 3, 4, 5 };
int _num =
(from nu in number
where nu < 4
select nu).Count();
Console.ReadKey();
}
}
}
將委托作為參數
很多運算符接受泛型委托作為參數
再來看看Count方法微峰,它被重載為兩個形式
//第一種:經常使用
public static int Count<TSource>(this IEnumerable<TSource> source);
//第二種,除了指示擴展類外抒钱,還接收一個泛型方法作為參數
public static int Count<TSource>(this IEnumerable<TSource> source, Func<TSource, bool> predicate);
第二種形式的Count方法蜓肆,如果想要傳遞一個放開方法作為參數颜凯,那就只能使用委托,因此Func<TSource, bool> predicate
指的就是一個委托形參(當然仗扬,這個委托也可以使用Lambda表達式代替)
示例:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace CodeForLinq
{
class Program
{
static void Main(string[] args)
{
int[] number = new int[] { 1, 2, 3, 4, 5 };
//Lambda會返回條件為Ture的值症概,這里會返回奇數,再統計個數
var countOdd = number.Count(n => n % 2 == 1);
Console.WriteLine("奇數個數:{0}",countOdd);
Console.ReadKey();
}
}
}
Linq預定義的委托類型
為了實現由編程人員提供代碼來指示標準運算會如何執(zhí)行它的操作早芭,標準運算符支持將委托作為參數來實現該目標
Linq定義了兩套泛型委托類型與標準查詢運算符一起使用彼城,即Fanc與Action委托,各有17個成員
因此在使用的時候要求:
- 我們用作實參的委托對象必須是這些類型或這些形式之一
- TResult代表返回值退个,并且總是在類型參數列表中的最后一個
示例:
public delegate TResult Func<in T, out TResult>(T arg);
Action與Func相似募壕,只是都沒有返回值
使用委托參數的示例:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace CodeForLinq
{
class Program
{
//返回是否是奇數
static bool IsOdd(int x)
{
return x % 2 == 1;
}
static void Main(string[] args)
{
int[] number = new int[] { 1, 2, 3, 4, 5 };
//創(chuàng)建一個Func<T, TR>類型委托
//T是類型參數
//TR是返回類型
Func<int, bool> my_func = new Func<int, bool>(IsOdd);
var countOdd = number.Count(my_func);
Console.WriteLine("奇數個數:{0}",countOdd);
Console.ReadKey();
}
}
}
使用Lambda表達式參數的示例
當標準運算符所需要的方法參數只用一次,并且塊內代碼只有一行语盈,那完成可以使用Lambda表達式來完成
示例 :
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace CodeForLinq
{
class Program
{
static void Main(string[] args)
{
int[] number = new int[] { 1, 2, 3, 4, 5 };
//使用Lambda表達式達到的效果是一樣的
var countOdd = number.Count(x => x % 2 == 1);
Console.WriteLine("奇數個數:{0}",countOdd);
Console.ReadKey();
}
}
}
也可以使用匿名方法:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace CodeForLinq
{
class Program
{
static void Main(string[] args)
{
int[] number = new int[] { 1, 2, 3, 4, 5 };
//使用匿名方法
Func<int, bool> my_func = delegate(int x){return x % 2 == 1;};
var countOdd = number.Count(my_func);
Console.WriteLine("奇數個數:{0}",countOdd);
Console.ReadKey();
}
}
}