【C#】LinQ

https://www.cnblogs.com/lifepoem/archive/2011/10/25/2223765.html

1.前言

sequence是实现了IEnumerable的对象
查询语法:类似SQL语句。
方法语法:System.Linq.Enumerable中定义了40个查询运算符。链式查询运算符。(https://www.cnblogs.com/lifepoem/archive/2011/10/27/2226556.html)
两种语法互补使用,方法语法使用的更多。
LINQ中最基本的数据单元是sequences和elements。

2.方法语法

(1)查询运算符签名

Func<TSource, bool>委托匹配 TSource => bool表达式,接受TSource输入参数,返回一个bool值。当结果为true时,表示该元素会包含在输出sequence中。

 public static IEnumerable<TSource> Where<TSource>
            (this IEnumerable<TSource> source, Func<TSource, bool> predicate)

//静态方法写法:
IEnumerable<string> query = System.Linq.Enumerable.Where(names, n=>n.Contains('a'));
//Where第一个参数为实现了IEnumerable的集合,第二个参数为返回值为bool的委托。

//扩展方法写法:
IEnumerable<string> query=name.Where(n=>n.Contains('a'));	//扩展方法的写法可以方便的写成链式操作
//使用var简写
var query = name.Where(n=>n.Contains('a'));

大部分查询运算符都接受一个lambda表达式作为参数,Lambda表达式格式为:(parameters) => expression-or-statement-block。
正是扩展方法让LINQ查询运算符的链接成为了可能。

(2)常用查询运算符

而对Where查询运算符来讲,它并不需要对输出element进行类型推断,因为它只是对输入elements进行过滤而不作转换,因此输出element和输入element具有相同的数据类型。
where 从句表示一种筛选过程,当条件满足的时候,这样的对象才可能被迭代返回出来。如果多次条件筛选的话,那就相当于是 && 处理一样的效果。

var student =
    from student in students
    where student.Age >= 18
    where student.Chinese + student.English + student.Math >= 180	//多次的Where相当于查询条件的&&
    select student.Name;

对于OrderBy查询运算符来讲,Func<TSource, TKey>把输入元素映射至一个排序键值。TKey由Lambda表达式的结果推断出来,比如我们可以按长度或按字母顺序对names数组进行排序。

First()——返回sequence中的第一个element
Last()——返回最后一个element
ElementAt()——返回某个index的element
OrderBy(lambda)——按照lambda指定的属性排序
Count()——对sequence计数
Min()——返回sequence的min
Max()
Contains(T ele)——返回是否包含某个ele
Any()——判断sequence中是否存在满足条件的ele

int[] intArray = new int[] { 0, 1, 2, 3 };
var items = intArray.Any(i => ((i % 2) == 0));
foreach (int item in items)
             Console.WriteLine(item);
          Console.ReadLine();

All()——判断sequence中是否所有ele都满足条件
Concat()——两个sequence连接在一起
Union()——两个sequence取并集

3.查询语法

(1)查询语法及使用

查询表达式要求要以Select或Group结束。

(2)方法语法和查询语法结合

方法语法有查询运算符,有的查询运算符没有对应的查询语法,如:Count()、First()、Last()。这种需要先以查询语法来写,最后结合查询运算符。适用于较为复杂的查询需求。

string[] names = { "Tom", "Dick", "Harry", "Mary", "Jay" };
 
            // 计算包含字母”a”的姓名总数
            int matches = (from n in names where n.Contains("a") select n).Count();     // 3
            // 按字母顺序排序的第一个名字
            string first = (from n in names orderby n select n).First();     // Dick

4.Linq的延迟执行

https://www.cnblogs.com/lifepoem/archive/2011/10/29/2228589.html
他们不是在查询创建的时候执行,而是在遍历的时候执行(换句话说,当enumerator的MoveNext方法被调用时)。

(1)重复查询

延迟执行带来的一个影响是,当我们重复遍历查询结果时,查询会被重复执行。
调用ToArray、ToList可令Linq立刻执行,将查询结果保存到Array和List<>中。

(2)局部变量捕获

如果查询的lambda表达式引用了程序的局部变量时,如果在查询定义之后改变了该变量的值,那么查询结果也会随之改变。

    int[] numbers = { 1, 2 };

            int factor = 10;
            IEnumerable<int> query = numbers.Select(n => n * factor);
            factor = 20;
            foreach (int n in query)
                Console.Write(n + "|");     // 20|40|

这个特性在我们通过foreach循环创建查询时会变成一个真正的陷阱。假如我们想要去掉一个字符串里的所有元音字母,我们可能会写出如下的query:

复制代码
IEnumerable query = "How are you, friend.";

        query = query.Where(c => c != 'a');
        query = query.Where(c => c != 'e');
        query = query.Where(c => c != 'i');
        query = query.Where(c => c != 'o');
        query = query.Where(c => c != 'u');

        foreach (char c in query) Console.Write(c); //Hw r y, frnd.

复制代码
尽管程序结果正确,但我们都能看出,如此写出来的程序不够优雅。所以我们会自然而然的想到使用foreach循环来重构上面这段程序:

复制代码
IEnumerable query = "How are you, friend.";

        foreach(char vowel in "aeiou")
            query = query.Where(c => c != vowel);

        foreach (char c in query) Console.Write(c); //How are yo, friend.

复制代码
结果中只有字母u被过滤了,咋一看,有没有吃一惊呢!但只要仔细一想就能知道原因:因为vowel定义在循环之外,所以每个lambda表达式都捕获了同一变量。当我们的query执行时,vowel的值是什么呢?不正是被过滤的字母u嘛。要解决这个问题,我们只需把循环变量赋值给一个内部变量即可,如下面的temp变量作用域只是当前的lambda表达式。

复制代码
IEnumerable query = "How are you, friend.";

        foreach (char vowel in "aeiou")
        {
            char temp = vowel;
            query = query.Where(c => c != temp);
        }

        foreach (char c in query) Console.Write(c); //Hw r y, frnd.

复制代码

(3)查询运算符的模型和延迟执行

和传统的集合类型如array,linked list不同,一个装饰者sequence并没有自己用来存放元素的底层结构,而是包装了我们在运行时提供的另外一个sequence。此后当我们从装饰者sequence中请求数据时,它就会转而从包装的sequence中请求数据。

比如调用Where会创建一个装饰者sequence,其中保存了输入sequence的引用、lambda表达式还有其他提供的参数。下面的查询对应的装饰者sequence如图所示:

    IEnumerable<int> lessThanTen = new int[] { 5, 12, 3 }.Where(n => n < 10);

当我们遍历lessThanTen时,实际上我们是在通过Where装饰者从Array中查找数据。

而查询运算符链接创建了一个多层的装饰者,每个查询运算符都会实例化一个装饰者来包装前一个sequence,比如下面的query和对应的多层装饰者sequence:

        IEnumerable<int> query = new int[] { 5, 12, 3 }
            .Where(n => n < 10)
            .OrderBy(n => n)
            .Select(n => n * 10);

在我们遍历query时,我们其实是在通过一个装饰者链来查询最初的array。
需要注意的是,如果在上面的查询后面加上一个转换运算符如ToList,那么query会被立即执行,这样,单个list就会取代上面的整个对象模型。

5.过滤运算符

https://www.cnblogs.com/lifepoem/archive/2011/11/16/2250676.html
image

6.排序、分组

  • OrderBy, ThenBy——升序排序
    ThenBy只会对那些在前一次排序中拥有相同键值的elements进行重新排序,我们可以连接任意数量的ThenBy运算符。
     // 先按长度排序,然后按第二个字符排序,再按第一个字符排序
            IEnumerable<string> query =
                names.OrderBy (s => s.Length).ThenBy (s => s[1]).ThenBy (s => s[0]);
    
  • OrderByDescending, ThenByDescending——对一个sequence按降序排序
  • Reverse——按倒序返回一个sequence

自定义排序规则

要排序先要定义比较规则。
基本型别都提供了默认的比较算法,如string提供了按字母进行比较,int提供了按整数大小进行比较。

  • 当我们创建了自己的实体类,如Student,默认想要对其按照年龄进行排序,则需要为实体类继承IComparable接口,实现CompareTo方法。
 class Student:IComparable
    {
        public string Name { get; set; }
        public int Age { get; set; }

        #region IComparable Members

        public int CompareTo(object obj)
        {
            Student student = obj as Student;
            if (Age > student.Age)
            {
                return 1;
            }
            else if (Age == student.Age)
            {
                return 0;
            }
            else
            {
                return -1;
            }
            //return Age.CompareTo(student.Age);
        }
        #endregion
    }
  • 如果不想使用年龄作为比较器了,那怎么办。这个时候IComparer的作用就来了,可使用IComparer来实现一个自定义的比较器。
class SortName: IComparer
    {
        #region IComparer Members

        public int Compare(object x, object y)
        {
            Student s1 = x as Student;
            Student s2 = y as Student;
            return s1.Name.CompareTo(s2.Name);
        }

        #endregion
    }

在排序的使用为Sort方法提供此比较器:

studentList.Sort(new SortName());
  • 上面的代码我们使用了一个已经不建议使用的集合类ArrayList。当泛型出来后,所有非泛型集合类已经建议不尽量使用了。进行了装箱和拆箱。而这是会影响性能的。如果我们的集合中有成千上万个复杂的实体对象,则在排序的时候所耗费掉的性能就是客观的。而泛型的出现,就可以避免掉拆箱和装箱。故上文代码中的ArrayList,应该换成List,对应的,我们就该实现IComparable和IComparer
class Student:IComparable<Student>
    {
        public string Name { get; set; }
        public int Age { get; set; }

        #region IComparable<Student> Members

        public int CompareTo(Student other)
        {
            return Age.CompareTo(other.Age);
        }

        #endregion
    }

    class SortName: IComparer<Student>
    {
        #region IComparer<Student> Members

        public int Compare(Student x, Student y)
        {
            return x.Name.CompareTo(y.Name);
        }
	}


posted @ 2023-04-27 14:05  徘徊彼岸花  阅读(13)  评论(0编辑  收藏  举报