C# 之LINQ
LINQ
允許詢問任何實現了IEnumerable<T>
接口的集合二,不管是數組,列表,或者XML DOM等。它帶來了在編譯階段的類型檢查和動態查詢的雙重好處。
基礎
LINQ的基本單元是序列(Sequence) 和元素(Element),序列即實現了IEnumerable<T>
接口的集合,元素就是該集合中的項。
詢問操作符(query operator)是改變序列的方法,典型的操作符接受輸入序列,返回輸出序列。在System.Linq
空間的Enumerable
類,有將近40多個詢問操作符,所有這些方法都是靜態擴展方法。
class Program
{
public static void Main()
{
List<int> nums = new List<int> { 1, 2, 3 };
var num = nums.Where(n => n >=2).Where(n=>n%2==0);
nums.Add(4);
foreach (var s in num) Console.WriteLine(s);
var num1 = from n in nums
where n >= 2 && n % 2 == 0
select n;
foreach (var s in num1) Console.WriteLine(s);
}
}
從上面的小例子可以看出:
- 詢問可以分為兩種方式:(1)
fluent syntax
,即不斷的調用詢問方法(2)query syntax
,即通過詢問語句來進行。這兩種方式是互補的。 - 只有輸出序列被枚舉時候(調用
MoveNext
方法)時,才去進行詢問操作
Fluent syntax
Chaining Query Operators
class Program
{
public static void Main()
{
string[] names = { "Tom", "Dick", "Harry", "Mary", "Jay" };
var query = names
.Where(n => n.Contains("a"))
.OrderBy(n => n.Length)
.Select(n => n.ToUpper());
foreach (var n in query) Console.WriteLine(n);
}
}
Query Expression

編譯器通過把query expression
轉換為fluent syntax
,這種轉換是一種比較機械的方式,就像把foreach
語句轉換為調用GetEnumerator
然后調用MoveNext
一樣。這意味着,任何用query syntax
可以詢問的都可以用fluent syntax
寫出來。
注意query expression必須以select 或group結尾。
Range Variable
緊跟from
后的變量就稱為range variable
。query expression也允許你用let,into,另外的from,join
等來引入新的range variable
.
Query syntax VS Fluent syntax
兩者各有優點。
下面情況用Query syntax
比較簡單:
let
語句引入新的變量SelectMany,join,GroupJoin
后面跟着outer range variable
對於包含單個操作符的,用fluent syntax
就比較簡潔。最后,有很多操作符是沒有對應的query syntax
,這就需要使用fluent syntax
,至少部分使用。
延遲執行
只有在被枚舉的時候才被執行,除了下面的情況:
- 返回標量或者單個元素的操作符,比如
First,Count
- 轉換操作,如
ToArray,ToList,ToDictionary,ToLookup
這些操作會馬上執行,不會延遲,因為結果的類型沒有支持延遲操作的機制。
延遲執行是非常重要的,因為它解耦了query construction
和query execution
.
Reevaluation
當query
被重新枚舉的時候,它會重新的執行。
public static void Main()
{
var numbers = new List<int> { 1, 2 };
IEnumerable<int> query = numbers.Select(n => n * 10);
foreach (int n in query) Console.Write(n + " | ");// 10|20|
numbers.Clear();
foreach (int n in query) Console.Write(n + " | ");//<nothing>
}
可以通過轉換操作符,來避免reevaluate
。
public static void Main()
{
var numbers = new List<int> { 1, 2 };
var query = numbers.Select(n => n * 10).ToList();//馬上執行
foreach (int n in query) Console.Write(n + " | ");// 10|20|
numbers.Clear();
foreach (int n in query) Console.Write(n + " | ");
}
Captured Variables
IEnumerable<char> query = "not what you might expect";
string vowels = "aeiou";
for (int i = 0; i < vowels.Length; i++)
{
//var vo = vowels[i];
query=query.Where(c => c != vowels[i]);
}
foreach (char c in query) Console.Write(c);
IEnumerable<char> query = "not what you might expect";
string vowels = "aeiou";
for (int i = 0; i < vowels.Length-1; i++)
{
//var vo = vowels[i];
query=query.Where(c => c != vowels[i]);
}
foreach (char c in query) Console.Write(c);
}
IEnumerable<char> query = "not what you might expect";
string vowels = "aeiou";
for (int i = 0; i < vowels.Length; i++)
{
var vo = vowels[i];
query =query.Where(c => c != vo);
}
foreach (char c in query) Console.Write(c);
Subqueries
var names = new List<string>{ "Tom", "Dick", "Harry", "Jay" };
var re = names.Where(n=>n.Length==names.OrderBy(n2=>n2.Length).First().Length);
names.Add("jim");
foreach (var r in re) Console.WriteLine(r + " ");
Subqueries
對於本地集合來講,是不高效的,因為外圍的query每次都會調用subqueries,在這個例子中就是每次都對names進行排序,求最小長度。
解決方法還是本地化:
var names = new List<string>{ "Tom", "Dick", "Harry", "Jay" }; int shortest = names.Min(n => n.Length); var re = names.Where(n=>n.Length==shortest); names.Add("jim"); foreach (var r in re) Console.WriteLine(r + " ");
建立復雜詢問的合成策略
- Progressive query construction :進行逐步的詢問組合
- Using the
into
keyword :利用into
關鍵字,形成新的range variable - Wrapping queries:
into
into
僅僅能出現在select,group
后,可以認為“新開辟”了一個詢問,允許引入新的where,orderby,select
等,但實際上還是一個詢問。
需要注意的是,在into
后,所有range variable
也就出了它們所在的范圍。
這里是非法的,因為into n2
后,后面的范圍就只能是n2了,n1就跑出了自身所在的范圍。
讓我們更正,再次運行程序:
public static void Main() { string[] names = { "Tom", "Dick", "Harry", "Mary", "Jay", "John" }; var query = from n1 in names select n1.ToUpper() into n2 where n2.Contains("O") select n2; foreach (var s in query) Console.WriteLine(s); }
運行正確。
Wrapping Queries
包裹詢問
可以將:
var tempQuery=tempQueryExpr;var finalQuery=from .... in tempQuery;
轉換為:
var finalQuery=from .... in tempQueryExpr;
wrapping query
在語義上等價於逐步詢問的組合,into
關鍵字(無中間變量)。
比如:
progressive construction
:
var query= from n in names select n.Replace("a","").Replace("e","").Replace("i","").Replace("o","") .Replace("u","");query=from n in query where n.Length>2 orderby n select n;
對應的wrapped queries
:
var query= from n1 in ( from n2 in names select n2.Replace("a","").Replace("e","").Replace("i","").Replace("o","") .Replace("u","")) where n1.Length>2 orderby n1 select n1;
wrapped queries
可能和subqueries
有點像,都有內外詢問,但subqueries
是在lambda表達式中。
Projection strategies 投射策略
對象實例器
至此,所有select
都是投射了標量元素類型,除了這些,還可以投射出更復雜的類型,比如,在第一步詢問中,我們希望既保留names原有的版本,又有去除元音的版本。
public class TempProjectionItem { public string Original; public string Vowelless; } class Program { public static void Main() { string[] names = { "Tom", "Dick", "Harry", "Mary", "Jay", "John" }; var temp = from n in names select new TempProjectionItem { Original = n, Vowelless = n.Replace("a", "").Replace("e", "").Replace("i", "").Replace("o", "") .Replace("u", "") };//實例的初始化語句 var query = from item in temp where item.Vowelless.Length > 2 select item.Original; foreach (var i in query) Console.WriteLine(i); }
Anonymous types匿名類型
匿名類型允許不用寫特定的類來結構化中間結果,比如上面的例子,我們可以不用寫TempProjectionItem
類,而用匿名類:
public static void Main() { string[] names = { "Tom", "Dick", "Harry", "Mary", "Jay", "John" }; var temp = from n in names select new { Original = n, Vowelless = n.Replace("a", "").Replace("e", "").Replace("i", "").Replace("o", "") .Replace("u", "") };//實例的初始化語句 var query = from item in temp where item.Vowelless.Length > 2 select item.Original; foreach (var i in query) Console.WriteLine(i); } }
可見,匿名類讓我們不用專門寫一個特定的類來存放中間結果,可以直接new實例化,並初始化。實際上,編譯器替我們創建了一個特定的類,這種情況下,我們必須使用var 關鍵字,因為我們不知道匿名類的類型。
我們可以用into
寫出整個的詢問:
string[] names = { "Tom", "Dick", "Harry", "Mary", "Jay", "John" }; var query = from n in names select new { Original = n, Vowelless = n.Replace("a", "").Replace("e", "").Replace("i", "").Replace("o", "") .Replace("u", "") } into temp where temp.Vowelless.Length > 2 select temp.Original; foreach (var i in query) Console.WriteLine(i);
let keyword
let
關鍵字在保留了原有的range variable
時,也引入了新的變量,這和into
是不一樣的,into
后就超出了原有的range variable
的作用范圍。
public static void Main() { string[] names = { "Tom", "Dick", "Harry", "Mary", "Jay", "John" }; var query = from n in names let voweless= n.Replace("a", "").Replace("e", "").Replace("i", "").Replace("o", "") .Replace("u", "") where voweless.Length > 2 select n;//Thanks to let, n is stil in scope foreach (var i in query) Console.WriteLine(i); }
編譯器通過創建一個匿名類,該匿名類既包含range variable也包含新的變量,也就是,轉換到上一個例子中去了。
可以在where
語句前后有任意多個let
語句,let
語句可以引用它之前的任意變量。
LINQ operators
標准詢問操作可以分為三類:
- Sequence in, Sequence out(序列到序列)
- Sequence in, single element or scaler value out(序列進,單個元素或標量值出)
- Nothing in, sequence out(生成模式)
Sequence in, Sequence out
Filtering
IEnumerable<TSource> 到 IEnumerable<TSource>
Filter 篩選,也就是返回原始元素的子集,運算操作有Where,Take,TakeWhile,Skip,SkipWhile,Distinct
。
- where
where bool-expression
string[] names = { "Tom", "Dick", "Harry", "Mary", "Jay" }; var query = names.Where(n => n.EndsWith("y")); foreach (var s in query) Console.WriteLine(s);
等效query語句:
string[] names = { "Tom", "Dick", "Harry", "Mary", "Jay" }; var query = from n in names where n.EndsWith("y") select n; foreach (var s in query) Console.WriteLine(s);
where也可以在query語句中出現多次:
string[] names = { "Tom", "Dick", "Harry", "Mary", "Jay" }; var query = from n in names where n.Length>3 let u=n.ToUpper() where u.EndsWith("Y") select u; foreach (var s in query) Console.WriteLine(s);
甚至:
string[] names = { "Tom", "Dick", "Harry", "Mary", "Jay" }; var query = from n in names where n.Length>3 where n.EndsWith("y") select n; foreach (var s in query) Console.WriteLine(s);
where
的predicte
可以選擇性的接受第二個參數,類型是int,含義是每個元素的索引:
string[] names = { "Tom", "Dick", "Harry", "Mary", "Jay" }; var query = names.Where((n, i) => i % 2 == 0); foreach (var s in query) Console.WriteLine(s);
對應的linq expression:
string[] names = { "Tom", "Dick", "Harry", "Mary", "Jay" }; var query = from n in names where names.ToList().IndexOf(n)%2==0 select n; foreach (var s in query) Console.WriteLine(s);
注意需要先把數組轉換為list,然后調用indexof方法求得其索引號。
Take 和Skip
Take
返回前n個元素,而丟棄剩余的元素,Skip
丟棄前n個元素,而返回剩余的元素。
string[] names = { "Tom", "Dick", "Harry", "Mary", "Jay" }; var query = names.Take(3); foreach (var s in query) Console.WriteLine(s);
string[] names = { "Tom", "Dick", "Harry", "Mary", "Jay" }; var query = names.Skip(3); foreach (var s in query) Console.WriteLine(s);
TakeWhile和SkipWhile
TakeWhile
是不斷的先take集合中的元素,直到集合中的元素不滿足一定的條件:
int[] numbers = { 3, 5, 2, 234, 4, 1 }; var takeWhileSmall = numbers.TakeWhile(n => n < 100); foreach (var i in takeWhileSmall) Console.WriteLine(i);
SkipWhile
是先不取集合中的元素,直到集合中的元素不滿足一定的條件:
int[] numbers = { 3, 5, 2, 234, 4, 1 }; var skipWhileSmall = numbers.SkipWhile(n => n < 100); foreach (var i in skipWhileSmall) Console.WriteLine(i);
Distinct
Distinct
返回輸入序列的去重后的序列,可以選擇性的傳入該方法一個定制化的equality comparer
(C#之集合 - JohnYang819 - 博客園 (cnblogs.com))。
public class Customer { public string LastName; public string FirstName; public Customer(string last, string first) { LastName = last; FirstName = first; } public override string ToString() { return FirstName + " " + LastName; } } public class LastFirstEqualityComparer : EqualityComparer<Customer> { public override bool Equals(Customer x, Customer y) => (x.LastName == y.LastName && x.FirstName == y.FirstName); public override int GetHashCode(Customer obj) => (obj.LastName + ";" + obj.FirstName).GetHashCode(); } class Program { public static void Main() { var c1 = new Customer("John", "Yang"); var c2 = new Customer("John", "Yang"); var c3 = new Customer("Tom", "Kong"); Customer[] cts = new Customer[] { c1, c2, c3 }; var c5 = cts.Distinct(); foreach (var c in c5) Console.Write(c+" "); Console.WriteLine(); var cmp = new LastFirstEqualityComparer(); var c4 = cts.Distinct(cmp); foreach (var c in c4) Console.Write(c+" "); } }
Projecting
映射
IEnumerable<TSource>到IEnumerable<TResult>
方法有Select,SelectMany
Select
用Select
,可以得到輸入序列相同數量的元素,不過每個元素都已經經過lambda函數的轉變。
Select
語句經常被映射為匿名類型:
var query= from f in FontFfamily.Families select new {f.Name,}
示例一
:
var nums = new int[] { 1, 2, 3, 4, 5 }; var query = from n in nums select new { num1 = n % 2, num2 = n % 3 }; foreach (var i in query) Console.WriteLine(i.num1.ToString() + " " + i.num2.ToString());
示例二
:
public class Customer { public string LastName; public string FirstName; public Customer(string last, string first) { LastName = last; FirstName = first; } public override string ToString() { return FirstName + " " + LastName; } } class Program { public static void Main() { var csts = new Customer[] { new Customer("John","Yang"), new Customer("Zank","Mofri"), new Customer("Padd","Jwee") }; var query = from c in csts select new { c.FirstName, c.LastName.Length }; foreach (var q in query) Console.WriteLine(q.FirstName + " " + q.Length.ToString()); } }
通過示例一,和示例二,可以看到匿名類型的字段的名稱如果要與原來的元素的類型的名稱不一樣就必須明確,如示例一,而如果要與原來的元素的可用字段名稱,就不必明確,如示例二,直接寫,直接調用。
如果沒有轉變的select
純粹是為了滿足query
必須要求以select或group
語句結束的要求。
indexed projection
string[] names = { "Tom", "Dick", "Harry", "Mary", "Jay" }; var query = names.Select((s, i) => i + "=" + s); foreach (var q in query) Console.WriteLine(q);
select subqueries 和object hierarchies
先看下System.IO
下的文件路徑和文件的對象的簡單使用(詳細的FileInfo,DirInfo用法見C#操作文件屬性 - 百度文庫 (baidu.com)):
public static void Main() { FileInfo[] dirs = new DirectoryInfo(@"G:\ipad電子書\C#\").GetFiles(); foreach (var d in dirs) Console.WriteLine(d); }
DirectoryInfo[] dirs = new DirectoryInfo(@"G:\ipad電子書\C#\").GetDirectories(); foreach (var d in dirs) Console.WriteLine(d);
DirectoryInfo[] dirs = new DirectoryInfo(@"G:\ipad電子書\C#\").GetDirectories(); var query = from d in dirs where (d.Attributes & FileAttributes.System) == 0 select new { DirectoryName = d.FullName, Created = d.CreationTime, Files = from f in d.GetFiles() where (f.Attributes & FileAttributes.Hidden)== 0 select new { FileName = f.Name, f.Length } }; foreach(var dirFiles in query) { Console.WriteLine("Directory: " + dirFiles.DirectoryName+" Created in "+dirFiles.Created); foreach (var file in dirFiles.Files) Console.WriteLine(" " + file.FileName + "Len:" + file.Length); } foreach(var d in dirs) { Console.WriteLine((int)d.Attributes); Console.WriteLine((int)FileAttributes.System); Console.WriteLine((d.Attributes & FileAttributes.System) == 0); }
從上面例子可以看出,可以在select
語句中內嵌一個subquery
來建立有層次的對象。
SelectMany
SelectMany
方法就是把挑選出來的子序列們“合成”一個“扁平”的序列。
先看一個Select
的示例:
string[] fullNames = { "Anne Williams", "John FFred Smith", "Sue Greeen" };
var query = fullNames.Select(name => name.Split());
foreach (var q in query)
{ foreach (var n in q)
Console.WriteLine(n);
}
在上面Select
中,形成的query
實際上是一個IEnumerable<string[]>
類型,所以要用雙重循環才能取遍元素。而用SelectMany
就可以直接形成扁平化的IEnumerable<string>
類型。
string[] fullNames = { "Anne Williams", "John FFred Smith", "Sue Greeen" };
var query = fullNames.SelectMany(name => name.Split());
foreach (var q in query)
{
Console.WriteLine(q);
}
等效的Query Syntax
(也被稱為額外生成器【additional generator】):
from identifier1 in enumerable-expression1from identifier2 in enumerable-expression2....
string[] fullNames = { "Anne Williams", "John FFred Smith", "Sue Greeen" };
var query = from fullName in fullNames
from name in fullName.Split()
select name;
foreach (var q in query)
{
Console.WriteLine(q);
}
multiple range variable
上面例子中,name和fullName
一直都在可被使用的范圍中,除非到最后,或者碰到into
語句,這點使得query syntax
在這方面上,相比fulent syntax 更有優勢。而SelectMany
其實是破壞了最外層元素的結構,對於上例來說就是IEnumerable<string[]>
被直接“拉平”為IEnumerable<string>
了。
比如:
string[] fullNames = { "Anne Williams", "John FFred Smith", "Sue Greeen" }; var query = from fullName in fullNames from name in fullName.Split() select name+" came from "+fullName; foreach (var q in query) { Console.WriteLine(q); }
實際上在幕后,編譯器做了很多技巧性的工作,允許我們同時訪問name
和fullName
。而對於SelectMany
只能訴諸於匿名類型而保持外層元素的結構:
string[] fullNames = { "Anne Williams", "John FFred Smith", "Sue Greeen" }; var query = fullNames.SelectMany(fName=>fName.Split().Select(name=>new { name, fName })). Select(x=>x.name+" Came from "+ x.fName); foreach (var q in query) { Console.WriteLine(q); }
當寫這種“額外生成器”時,有兩種基本模式:(1)延申及扁平化子序列(上例);(2)笛卡爾積或稱交叉積;其中第一種模式兩個變量是遞進的關系;第二種模式的兩個變量是平級的關系.
- 笛卡爾積例子
int[] nums = { 1, 2, 3 }; string[] letters = { "a", "b" }; var query = from n in nums from l in letters select n.ToString() + l; foreach (var r in query) Console.WriteLine(r);
string[] players = { "Tom", "Jay", "Mary" }; var query = from p1 in players from p2 in players where p1.CompareTo(p2)<0 select p1+" VS "+p2; foreach (var r in query) Console.WriteLine(r);
改變過濾條件:
string[] players = { "Tom", "Jay", "Mary" }; var query = from p1 in players from p2 in players where p1.CompareTo(p2)>0 select p1+" VS "+p2; foreach (var r in query) Console.WriteLine(r);
Joining 聯結
Query Syntax
:
from outer-var in outer-enumerablejoin inner-var in inner-enumerable on outer-key-expr equals inner-key-expr [into identifier]
join
和GroupJoin
將兩個輸入序列合成一個單獨的序列,Join
返回的是flat output
,GroupJoin
返回的是hierarchical output
.
Join
和GroupJoin
對於本地 in-memory
集合的優勢在於更高效,原因在於首先將inner sequence加載,避免了不斷重復的枚舉。缺點是僅僅提供了inner,left outer joins.
class Program { public class Student { public int StID; public string LastName; } public class CourseStudent { public string CourseName; public int StID; } static Student[] students=new Student[] { new Student{StID=1,LastName="Carson"}, new Student{StID=2,LastName="Klassen"}, new Student{StID=3,LastName="Fleming"}, }; static CourseStudent[] studentsInCourses = new CourseStudent[] { new CourseStudent{CourseName="Art",StID=1}, new CourseStudent{CourseName="Art",StID=2}, new CourseStudent{CourseName="History",StID=1}, new CourseStudent{CourseName="History",StID=3}, new CourseStudent{CourseName="Physics",StID=3}, }; public static void Main() { var query = from s in students join c in studentsInCourses on s.StID equals c.StID where c.CourseName == "History" select s.LastName; foreach (var q in query) Console.WriteLine(q); } }
方法一
:
var query = from s in students join c in studentsInCourses on s.StID equals c.StID select s.LastName+" select Course "+c.CourseName; foreach (var q in query) Console.WriteLine(q);
方法二
:
var query = from s in students from c in studentsInCourses where s.StID == c.StID select s.LastName+" select Course "+c.CourseName; foreach (var q in query) Console.WriteLine(q);
方法一就是join方法,方法二是“等效”的from subsequence方法,方法一比較高效,另外join可以使用多次。
Joining on multiple keys
from x in sequenceXjoin y in sequenceY on new{K1=x.Prop1,k2=x.Prop2} equals new{K1=y.Prop3,K2=y.Prop4}....
Joining in fluent syntax
from c in customersjoin p in purchases on c.ID equals p.CustomerIDselect new {c.Name,P.Description,p.Price}
的等效fluent syntax
是:
customers.Join(purchases, c=>c.ID, p=>p.CustomerID, (c,p)=>new{c.Name,p.Description,p.Price});
GroupJoin
GroupJoin
與Join
的query syntax基本一樣,除了它必須后面加上into
語句。
與僅能跟在Select
和Group
后面的into
不一樣,跟在Join
后面的into
語句表示GroupJoin

public static void Main()
{
var query = from s in students
join c in studentsInCourses on s.StID equals c.StID
into courses
select new { s.LastName, courses };
foreach (var q in query)
{
Console.WriteLine(q.LastName+":");
foreach (var p in q.courses)
Console.Write(p.CourseName+",");
Console.WriteLine(); } }
var a = new List<int> { 0, 1 };
var b = new List<int> { 1, 2, 3, 4, 5 };
var c = from i in a
join j in b on i equals j % 2
into k
select new { num0 = i, num1 = k };
foreach(var i in c)
{
Console.WriteLine(i.num0.ToString()+":");
foreach(var ele in i.num1)
{
Console.WriteLine(ele);
}
}
默認地,GroupJoin
表現的是left outer join
行為,即對outer sequence全部包含,對inner sequence不保證完全包含,
如果想要得到inner join
,即inner sequence是空的則被排除在外,就需要對inner sequence進行過濾了,
關於left outer join,right outer join,inner join
可參考(9條消息) SQL 內連接(inner join)與外連接(left outer join 、right outer join )區別_Shirley的博客-CSDN博客_left outer
對於本例來講,我們現在增加一個新的student
,再進行GroupJoin
:
class Program
{
public class Student
{
public int StID;
public string LastName;
}
public class CourseStudent
{
public string CourseName;
public int StID;
}
static Student[] students=new Student[]
{
new Student{StID=1,LastName="Carson"},
new Student{StID=2,LastName="Klassen"},
new Student{StID=3,LastName="Fleming"},
new Student{StID=4,LastName="JohnYang"}
};
static CourseStudent[] studentsInCourses = new CourseStudent[]
{
new CourseStudent{CourseName="Art",StID=1},
new CourseStudent{CourseName="Art",StID=2},
new CourseStudent{CourseName="History",StID=1},
new CourseStudent{CourseName="History",StID=3},
new CourseStudent{CourseName="Physics",StID=3},
};
public static void Main()
{
var query = from s in students
join c in studentsInCourses on s.StID equals c.StID
into courses
select new { s.LastName, courses };
foreach (var q in query)
{
Console.WriteLine(q.LastName+":");
foreach (var p in q.courses)
Console.Write(p.CourseName+",");
Console.WriteLine();
}
}
}
發現沒有Course
的JohnYang
也被選了出來,這也證實了GroupJoin
的確是默認left outter join
.
現在,我們進行過濾:
var query = from s in students
join c in studentsInCourses on s.StID equals c.StID
into courses
where courses.Any()
select new { s.LastName, courses };
foreach (var q in query)
{
Console.WriteLine(q.LastName+":");
foreach (var p in q.courses)
Console.Write(p.CourseName+",");
Console.WriteLine();
}
發現沒有Course
的JohnYang
已經被排除在外,也證明了我們已經實現了inner join.
需要注意的是,對於GroupJoin
,在into
后面的語句實際上是針對的已經聯結過后的inner sequence,如果要對原有的單獨的作為inner sequence的元素進行過濾,則需要在join前進行過濾,比如:
from c in Customers
join p in puchases.Where(p2=>p2.price>1000)
on c.ID equals p.CustomerID
into cusPurchases
var students = new[]
{
new {id=1,ln="Carson"},
new {id=2,ln="Klasson"},
new {id=3,ln="Fleming"}
};
var studentsInCourses = new[]
{
new {cn="Art",id=1},
new {cn="History",id=2},
new {cn="History",id=1},
new {cn="Physic",id=3},
new {cn="Art",id=3}
};
var query = from s in students
from sc in studentsInCourses
where sc.cn == "Art"
where sc.id == s.id
select s;
foreach (var s in query) Console.WriteLine(s.ln);
Zip
int[] numbers = { 3, 5, 7 }; string[] words = { "Three", "Five", "Seven" }; var zip = numbers.Zip(words); foreach(var z in zip) { Console.WriteLine(z.First.ToString() + " is " + z.Second); }
int[] numbers = { 3, 5, 7 };
string[] words = { "Three", "Five", "Seven" };
var zip = numbers.Zip(words,(n,w)=>n.ToString()+" is "+w);
foreach(var z in zip)
{
Console.WriteLine(z);
}
Grouping
query syntax
group element-expression by key-expression
groupby
可以把輸入序列組成為一組的序列,該序列帶有Key
屬性,該屬性也是通過GroupBy
方法得到的。
string[] files = Directory.GetFiles(@"C:\Users\PC\Downloads\");
var query = files.GroupBy(file => Path.GetExtension(file));
foreach(IGrouping<string,string> grouping in query)
{
Console.WriteLine("Extension:" + grouping.Key);
foreach (string filename in grouping)
Console.WriteLine(" -" + filename);
}
等效的Query syntax
:
string[] files = Directory.GetFiles(@"C:\Users\PC\Downloads\");
//var query = files.GroupBy(file => Path.GetExtension(file));
var query = from f in files
group f by Path.GetExtension(f);
foreach(IGrouping<string,string> grouping in query)
{
Console.WriteLine("Extension:" + grouping.Key);
foreach (string filename in grouping)
Console.WriteLine(" -" + filename);
}
默認地,groupby
僅僅是對原有的元素進行了分組,而並沒有改變元素本身,但這並不意味着不能改變,可以傳入第二個參數elementSelector
來做到這點,或者直接在Query Syntax
中進行轉換:
string[] files = Directory.GetFiles(@"C:\Users\PC\Downloads\");
//var query = files.GroupBy(file => Path.GetExtension(file));
var query = from f in files
group f.ToUpper() by Path.GetExtension(f);
foreach(IGrouping<string,string> grouping in query)
{
Console.WriteLine("Extension:" + grouping.Key);
foreach (string filename in grouping)
Console.WriteLine(" -" + filename);
}
string[] files = Directory.GetFiles(@"C:\Users\PC\Downloads\");
var query = files.GroupBy(file => Path.GetExtension(file),file=>file.ToUpper());
//var query = from f in files
// group f.ToUpper() by Path.GetExtension(f);
foreach (IGrouping<string,string> grouping in query)
{
Console.WriteLine("Extension:" + grouping.Key);
foreach (string filename in grouping)
Console.WriteLine(" -" + filename);
}
Grouping by multiple keys
通過匿名類型,可以通過復雜的key值來進行分組:
string[] names = { "Tom", "Jason", "John", "Peter", "Joee", "Lucy" };
var query = from n in names
group n by new { fl = n[0], len = n.Length };
foreach (var grouping in query)
{
Console.WriteLine(grouping.Key.fl + " " + grouping.Key.len.ToString());
foreach (var g in grouping)
Console.WriteLine(g);
}
Set operators
Concat and Union
int[] seq1 = { 1, 2, 3 }, seq2 = { 3, 4, 5 };
var concat = seq1.Concat(seq2);
var union = seq1.Union(seq2);
foreach (var s in concat) Console.Write(s + " ");
Console.WriteLine();
foreach (var s in union) Console.Write(s + " ");
MethodInfo[] methods = typeof(string).GetMethods();
PropertyInfo[] props = typeof(string).GetProperties();
var both = methods.Union<MemberInfo>(props);
foreach (var b in both) Console.WriteLine(b.Name);
Intersect and Except
int[] seq1 = { 1, 2, 3 }, seq2 = { 3, 4, 5 };
var comm = seq1.Intersect(seq2);
var dif1 = seq1.Except(seq2);
var dif2 = seq2.Except(seq1);
foreach (var s in comm) Console.Write(s + " ");
Console.WriteLine();
foreach (var s in dif1) Console.Write(s + " ");
Console.WriteLine();
foreach (var s in dif2) Console.Write(s + " ");
Conversion Method
OfType
和Cast
OfType
和Cast
接受非泛型的IEnumerable
集合,返回泛型IEnumerable<T>
。
var classicList = new ArrayList();
classicList.Add("string");
classicList.Add(23);
foreach (var s in classicList) Console.WriteLine(s);
ArrayList
接受object類型的元素,為非泛型可變數組,用ArrayList
來學習OfType
和Cast
。
Cast
和OfType
當遇到不兼容的類型的行為不同,OfType
會忽略這些不兼容的類型,而Cast
則會拋出異常。
OfType
:
var classicList = new ArrayList();
classicList.Add("string");
classicList.Add(23);
classicList.Add(34);
classicList.Add("string2");
var seq = classicList.OfType<int>();
foreach (var s in seq) Console.WriteLine(s);
Console.WriteLine("***************");
var seq1 = classicList.OfType<string>();
foreach (var s in seq1) Console.WriteLine(s);
Cast
:
var classicList = new ArrayList();
classicList.Add("string");
classicList.Add(23);
classicList.Add(34);
classicList.Add("string2");
var seq = classicList.Cast<int>();
foreach (var s in seq) Console.WriteLine(s);
Console.WriteLine("***************");
var seq1 = classicList.Cast<string>();
foreach (var s in seq1) Console.WriteLine(s);
需要注意的是Cast
和OfType
是檢驗每個元素是否能轉換為目標類型(用is來判斷),然后采取不同的行為。
var classicList = new ArrayList();
classicList.Add("string");
classicList.Add(23.3);
classicList.Add(34.4);
classicList.Add("string2");
var seq = classicList.OfType<int>();
foreach (var s in seq) Console.WriteLine(s);
Console.WriteLine("***************");
var seq1 = classicList.OfType<string>();
foreach (var s in seq1) Console.WriteLine(s);
Console.WriteLine(23.2 is int);
解決方法
:
int[] integers = { 1, 2, 3 };
var castLong = integers.Select(s => (long)s);
foreach (var s in castLong) Console.WriteLine(s);
ToArray,ToList,ToDictionary,ToLookup
ToArray,ToList,ToDictionary,ToLookup
強制把枚舉值作為輸出序列馬上輸出。
int[] integers = { 1, 2, 3 };
//var castLong = integers.Select(s => (long)s);
var castLong = integers.ToList();
integers[0] = 100;
foreach (var s in castLong) Console.WriteLine(s);
ToDictionary
:
int[] integers = { 1, 2, 3 };
//var castLong = integers.Select(s => (long)s);
var castLong = integers.ToDictionary(s=>s*2);
integers[0] = 100;
foreach (var s in castLong)
Console.WriteLine(s.Key.ToString() + ":" + s.Value.ToString());
int[] integers = { 1, 2, 3 };
//var castLong = integers.Select(s => (long)s);
var castLong = integers.ToDictionary(s=>s%2);//鍵重復,重新添加,則報錯
integers[0] = 100;
foreach (var s in castLong)
Console.WriteLine(s.Key.ToString() + ":" + s.Value.ToString());
ToLookup
int[] integers = { 1, 2, 3 };
//var castLong = integers.Select(s => (long)s);
var castLong = integers.ToLookup(s=>s%2);
integers[0] = 100;
foreach (var s in castLong)
{
Console.WriteLine("key:" + s.Key.ToString());
foreach (var p in s)
Console.Write(p + " ");
Console.WriteLine();
}
Element operators
主要方法有First, FirstOrDefault, Last, LastOrDefault, Single, SingleOrDefault, ElementAt, ElementAtOrDefault, DefaultIfEmpty
以OrDefault
結尾的都是返回源類型的默認值,而非拋出異常,如果輸入序列為空,或沒有元素可以匹配。
First,Last, Single
int[] numbers= { 1, 2, 3,4,5 };
int first = numbers.First();//1
int last = numbers.Last();//5
int firstEven = numbers.First(n => n % 2 == 0);//2
int lastEven = numbers.Last(n => n % 2 == 0);//4
//int firstBigError = numbers.First(n => n > 10);//Exception
int firstBigNumber = numbers.FirstOrDefault(n => n > 10);//0
int onlyDivBy3 = numbers.Single(n => n % 3 == 0);//3
//int divBy2Err = numbers.Single(n => n % 2 == 0);//Error
//int singleError = numbers.Single(n => n > 10);//Error
int noMatches = numbers.SingleOrDefault(n => n > 10);//0
//int divBy2Error = numbers.SingleOrDefault(n => n % 2 == 0);//Error
var total = new int[] {first,last,firstEven,lastEven,firstBigNumber,
onlyDivBy3,noMatches};
foreach (var t in total) Console.WriteLine(t);
Single
是“最挑剔”的方法,必須是僅有一個匹配的元素,多了少了都報錯,而SingleOrDefault
則在此基礎上,對於沒有匹配的,返回一個默認值,而對於有多個的還是拋出錯誤。
ElementAt
int[] numbers = { 1, 2, 3, 4, 5 };
int third = numbers.ElementAt(2);//3
//int ten=numbers.ElementAt(9);//Exception
int ten = numbers.ElementAtOrDefault(9);//0
var tot = new int[] { third, ten };
foreach (var t in tot) Console.WriteLine(t);
Aggregation Methods
聚合
IEnumerable<TSource>到Scalar
Count
int digitCount = "Pa55wo0rd1".Count(c => char.IsDigit(c));
Console.WriteLine(digitCount);
LongCount
也是相同的作用,只不過返回64位整數,允許超過20億個元素。
Min
,Max
int[] numbers = { 28, 32, 14 };
int max = numbers.Max(n => n % 10);//8
int min = numbers.Min(n => n % 10);//2
var m = new int[] { max, min };
foreach (var mm in m) Console.WriteLine(mm);
Sum
,Average
Sum 和Average
對於它們的類型是相當嚴格的,比如
int avg=new int[]{3,4}.Average();//不能編譯

Quantifiers
bool hasTr = new int[] { 2, 3, 4 }.Contains(3);//true
bool hasAt = new int[] { 2, 3, 4 }.Any(n => n == 3);//true
bool hasABig = new int[] { 2, 3, 4 }.Any(n => n > 10);//false
SequenceEqual
比較兩個序列,如果每個元素都一樣,則返回true
var a = new int[] { 1, 2, 3 };
var b = new int[] { 1, 2, 3 };
var c = new List<int> { 1, 2, 3 };
var d = new List<int> { 1, 2, 3 };
Console.WriteLine(c.SequenceEqual(b));
Console.WriteLine(a.SequenceEqual(b));
IStructuralEquatable e = (IStructuralEquatable)b;
Console.WriteLine(e.Equals(a,EqualityComparer<int>.Default));
Console.WriteLine(d.Equals(a));
IStructuralEquatable f = (IStructuralEquatable)c;
Console.WriteLine(f.Equals(d,EqualityComparer<int>.Default));
Generation Methods
Empty,Repeat,Range
是Enumerable
的靜態方法。
- Empty
int[][] numbers =
{
new int[]{1,2,3},
new int[]{4,5,6},
null,
};
var flat = numbers.SelectMany(inner => inner);
foreach (var s in flat) Console.WriteLine(s);
可見,null使最后拋出個異常。
使用Empty
加上??
就可以解決這個問題。
int[][] numbers =
{
new int[]{1,2,3},
new int[]{4,5,6},
null,
};
var flat = numbers.SelectMany(inner => inner??Enumerable.Empty<int>());
foreach (var s in flat) Console.WriteLine(s);
Range 和Repeat
foreach (int i in Enumerable.Range(5, 3)) Console.Write(i.ToString() + ",");
Console.WriteLine();
foreach (bool x in Enumerable.Repeat(true, 4)) Console.Write(x + ",");