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

默认地,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
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:UsersPCDownloads");
            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:UsersPCDownloads");
            //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:UsersPCDownloads");
            //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:UsersPCDownloads");
            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

##### 愿你一寸一寸地攻城略地,一点一点地焕然一新 #####
原文地址:https://www.cnblogs.com/johnyang/p/15436854.html