C# 之LINQ


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);
        }

    }

image-20211019232413025

從上面的小例子可以看出:

  • 詢問可以分為兩種方式:(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);
        }

    }

image-20211019233802913

Query Expression

image-20211020224525877

編譯器通過把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 constructionquery 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>
        }

image-20211020235058331

可以通過轉換操作符,來避免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 + " | ");
        }

image-20211020235349269

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);

image-20211021122654937

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);
        }

image-20211021122745487

 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);

image-20211021122832390

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 + " ");

image-20211024090234648

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也就出了它們所在的范圍。

image-20211021210809611

這里是非法的,因為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);        }

image-20211021211005182

運行正確。

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;

image-20211021215723449

對應的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;

image-20211021220125925

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);        }

image-20211021225708219

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);        }    }

image-20211021230142745

可見,匿名類讓我們不用專門寫一個特定的類來存放中間結果,可以直接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);

image-20211021231759083

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);        }

image-20211021232928205

編譯器通過創建一個匿名類,該匿名類既包含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);

image-20211024090142765

等效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);

wherepredicte可以選擇性的接受第二個參數,類型是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);

image-20211024090702849

string[] names = { "Tom", "Dick", "Harry", "Mary", "Jay" };                        var query = names.Skip(3);            foreach (var s in query) Console.WriteLine(s);

image-20211024090747139

  • 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);

image-20211024091153812

SkipWhile是先不取集合中的元素,直到集合中的元素不滿足一定的條件:

 int[] numbers = { 3, 5, 2, 234, 4, 1 };            var skipWhileSmall = numbers.SkipWhile(n => n < 100);            foreach (var i in skipWhileSmall) Console.WriteLine(i);

image-20211024091354874

  • 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+" ");        }    }

image-20211024101251702

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());

image-20211024104931670

示例二:

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());        }    }

image-20211024105537396

通過示例一,和示例二,可以看到匿名類型的字段的名稱如果要與原來的元素的類型的名稱不一樣就必須明確,如示例一,而如果要與原來的元素的可用字段名稱,就不必明確,如示例二,直接寫,直接調用。

如果沒有轉變的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);

image-20211024111925971

  • 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);        }

image-20211024112945911

DirectoryInfo[] dirs = new DirectoryInfo(@"G:\ipad電子書\C#\").GetDirectories();            foreach (var d in dirs) Console.WriteLine(d);

image-20211024144153391

            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);            }

image-20211024222025025

從上面例子可以看出,可以在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);            
}

image-20211025224402260

在上面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); 
}

image-20211025224810744

等效的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);   
}

image-20211025225126561

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);            }

image-20211025230314866

實際上在幕后,編譯器做了很多技巧性的工作,允許我們同時訪問namefullName。而對於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);            }

image-20211025231114938

當寫這種“額外生成器”時,有兩種基本模式:(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);

image-20211025232829650

改變過濾條件:

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);

image-20211025232919252

Joining 聯結

Query Syntax:

from outer-var in outer-enumerablejoin inner-var in inner-enumerable on outer-key-expr equals inner-key-expr [into identifier]

joinGroupJoin將兩個輸入序列合成一個單獨的序列,Join返回的是flat output,GroupJoin返回的是hierarchical output.

JoinGroupJoin對於本地 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);        }    }

image-20211026213801508

方法一:

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);

image-20211026214412277

方法二:

 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);

image-20211026214412277

方法一就是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

GroupJoinJoin的query syntax基本一樣,除了它必須后面加上into語句。

與僅能跟在SelectGroup后面的into不一樣,跟在Join后面的into語句表示GroupJoin

image-20211026225931878
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();            }        }

image-20211026230724236


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不保證完全包含,

image-20211026231503289

如果想要得到inner join,即inner sequence是空的則被排除在外,就需要對inner sequence進行過濾了,

image-20211026231633570

關於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();
            }
        }

    }

image-20211026231839512

發現沒有CourseJohnYang也被選了出來,這也證實了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();
            }

image-20211026232044542

發現沒有CourseJohnYang已經被排除在外,也證明了我們已經實現了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);

image-20211205175539145

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);            }

image-20211027124934892

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);
            }

image-20211027125050602

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);
            }

image-20211027205548189

等效的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);
            }

image-20211027211007075

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);
            }

image-20211027214544125

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 + " ");

image-20211027215114609

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);

image-20211027220302730

  • 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 + " ");

image-20211027220737490

Conversion Method

  • OfTypeCast

OfTypeCast接受非泛型的IEnumerable集合,返回泛型IEnumerable<T>

var classicList = new ArrayList();
            classicList.Add("string");
            classicList.Add(23);
            foreach (var s in classicList) Console.WriteLine(s);

image-20211028204745399

ArrayList接受object類型的元素,為非泛型可變數組,用ArrayList來學習OfTypeCast

CastOfType當遇到不兼容的類型的行為不同,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);

image-20211028205823904

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);

image-20211028205746188

需要注意的是CastOfType是檢驗每個元素是否能轉換為目標類型(用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);

image-20211028210401478

image-20211028210832828

解決方法:

int[] integers = { 1, 2, 3 };
            var castLong = integers.Select(s => (long)s);
            foreach (var s in castLong) Console.WriteLine(s);

image-20211028210731793

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);

image-20211028211213505

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());

image-20211028211429643

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());

image-20211028211546664

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();
            }    

image-20211028211814184

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);

image-20211028221218430

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);

image-20211028222017282

Aggregation Methods聚合

IEnumerable<TSource>到Scalar

  • Count
int digitCount = "Pa55wo0rd1".Count(c => char.IsDigit(c));
            Console.WriteLine(digitCount);

image-20211028222432304

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);

image-20211028223101902

  • SumAverage

Sum 和Average對於它們的類型是相當嚴格的,比如

int avg=new int[]{3,4}.Average();//不能編譯
image-20211028224512459
  • 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));

image-20211028234858705

Generation Methods

Empty,Repeat,RangeEnumerable的靜態方法。

  • 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);

image-20211028235350730

可見,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);

image-20211028235544786

  • 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 + ",");

image-20211028235814196


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM