知识需要不断积累、总结和沉淀,思考和写作是成长的催化剂

内容目录

一、Lambda表达式1、匿名方法2、Lambda表达式二、Linq概述三、查询操作符1、linq初见2、常用查询操作符筛选排序分组连接合并分页聚合转换四、并行Linq五、表达式树1、认识表达式目录树2、拼装表达式树3、应用六、小结

一、Lambda表达式

1、匿名方法

使用delegate的时候很多时候没必要使用一个普通方法,因为这个方法只有delegate会用,并且只用一次,这时候使用匿名方法最合适。
匿名方法就是没有名字的方法。示例中就是用一个匿名方法创建委托实例,我们无需在写一个具名方法给委托,使代码更简洁和可读。匿名方法也是在调用时执行,在myDele(1,"test")处调用。(用反编译器看一下还是会生成一个具名方法的,只不过在编译器内部使用)。

delegate bool MyDelegate(int i, string s);
MyDelegate myDele = delegate (int i, string s)
{
    Console.WriteLine($"我是匿名方法,参数值{i},{s}");
    return true;
};
bool b = myDele(1, "test");

2、Lambda表达式

函数式编程,在C#3.0开始,我们有了Lambda表达式代替匿名方法,它比匿名方法更加简单。Lambda运算符“=>”(发音goesto)的左边列出了需要的参数,右边是利用该参数方法的实现代码。

Action<string> a1 = delegate (string s) { Console.WriteLine(s); };
a1("匿名方法");
Action<string> a2 =  (string s)=> { Console.WriteLine(s); };
a1("Lambda表达式");
Action<string> a3 = s => { Console.WriteLine(s); };
a3("Lambda表达式,有一个参数的可以简写不要小括号,参数类型会自动推断");
Action<string> a4 = s =>  Console.WriteLine(s);
a4("Lambda表达式,方法体只有一行,连花括号也可以省略");

另一点,通过Lambda表达式可以访问Lambda表达式块外部的变量。这是一个非常好的功能,但如果未正确使用,也会非常危险。

int sommVal = 5;
Func<int, int> f = x => x + sommVal; sommVal = 7;
Console.WriteLine(f(3));

如果外部修改了sommVol值就会影响Lambda表达式的输出,特别是在多线程中,可能无法确定当前的sommVal值。
Lambad表达式内部是如何使用外部的变量呢?首先编译器会创建一个匿名类,然后将使用到的外部变量当做匿名类的构造函数的参数,当调用时候,就创建匿名类的一个实例,并传递调用该方法时外部变量的值。

二、Linq概述

Linq(language integrated query)语言集成查询集成了C#编程语言中的查询语法,使之可以使用相同的语法访问不同的数据源
根据数据源的不同,Linq可分为linq to object,linq to sql,linq to xml,你也可以扩展linq to excel,to everything。为不同的数据源提供相同的查询接口即可。

三、查询操作符

1、linq初见

现在我们有如下实体的集合

public class Student
{
    public int Id { get; set; }
    public int ClassId { get; set; }
    public string Name { get; set; }
    public int Age { get; set; }
}
List<Student> studentLst = new List<Student>();

假设studentLst里已经有一些数据,然后需要查询出年纪小于25的学生。有很多方法,可以循环列表挑出age<25的学生,可以使用List的FindAll,Where等方法,看起来像下面这样(注意只有在访问list中数据时,才会去执行过滤条件查询,延迟查询)

var list = studentLst.Where<Student>(s => s.Age < 25); 
foreach (var item in list)
{
    Console.WriteLine("Name={0}  Age={1}", item.Name, item.Age);
}

where扩展方法的内部逻辑大概像这样,foreach循环调用过滤的委托方法,yield关键字语法糖包装了一些复杂行为,包括会初始化一个IEnumerable类,然后给添加内容。

public static IEnumerable<T> Where<T>(this IEnumerable<T> source, Func<T, bool> func)
{
    if (source == null)
    {
        throw new Exception("source is null");
    }
    if (func == null)
    {
        throw new Exception("func is null");
    }     foreach (var item in source)
    {
        if (func.Invoke(item))
        {
            yield return item;
        }
    }
}

那么用Linq如何查询呢?

var list = from s in studentList
           where s.Age < 25
           select s; foreach (var item in list)
{
    Console.WriteLine("Name={0}  Age={1}", item.Name, item.Age);
}

From、where、select都是预定义的关键字,查询表达式必须以from开头,以select或group子句结束,中间可以使用where、orderby、join等。from子句引入数据源studentList和范围变量s,s就像foreach循环中的迭代变量。
同样的,在运行期间定义查询表达式时,查询不会立即运行,在迭代数据项时运行。

2、常用查询操作符

筛选

最常见的查询操作就是以布尔表达式的形式应用筛选器。通过where子句筛选表达式为true的结果

var list = from s in studentList
           where s.Age < 25 && s.ClassId==1
           select s;
排序

Orderby子句根据要排序类型的默认比较器,对返回序列中的元素进行排序

var list = from s in studentList
           where s.Age < 25
           orderby s.Name ascending
           select s;

和list的以下方法类似,就是把关键字解析为方法,随着查询越来越复杂,linq这种类似sql语句就表现的更加简洁直观

var list = studentList.Where(s => s.Age < 25).OrderByDescending(s => s.Name).Select(s => s);
分组

group 子句用于对根据您指定的键所获得的结果进行分组。示例中into关键字创建进一步查询的标识,用select子句创建了一个带key和maxAge属性的匿名类型,返回每个班级中年龄小于25岁的最大年龄。

var list = from s in studentList
           where s.Age < 25
           group s by s.ClassId into sg
           select new
           {
               key = sg.Key,
               maxAge = sg.Max(t => t.Age)
           };
foreach (var item in list)
{
    Console.WriteLine($"key={item.key}  maxAge={item.maxAge}");
}
连接

使用join子句可以根据特性的条件合并两个数据源。例如通过连接查询选择相同课程的学生

List<Class> classList = new List<Class>(){
        new Class()
        {
            Id=1,
            ClassName="高数"
        },
        new Class()
        {
            Id=2,
            ClassName="毛概"
        }
};
var list = from s in studentList
           join c in classList on s.ClassId equals c.Id
           select new
           {
               Name = s.Name,
               CalssName = c.ClassName
           };
foreach (var item in list)
{
    Console.WriteLine($"Name={item.Name},CalssName={item.CalssName}");
}
合并

zip方法是.NET4新增的,允许用一个函数把两个序列合并为一个。第一个集合中的第一项会与第二个集合中的第一项合并,第一个集合中的第二项与第二个集合中的第二项合并,以此类推。如果两个集合的项目不同,zip方法就在到达较小集合的末尾时停止

var list = from s in studentList
           where s.Age < 25
           select s;
var list2 = from s in studentList
            where s.Age < 25
            select s;
var lst = list.Zip(list2, (first, second) => first.Name + "," + second.Name);
分页

扩展方法Take()和Skip()等的分区操作用于分页。使用时把扩展方法take、skip添加到查询的最后,skip方法会忽略根据页面大小和实际页数计算出的项数,再使用take方法根据页面大小提取一定数量的项

int pageSize = 5;
int pageIdx = 0;
var list = (from s in studentList
            where s.Age < 25
            select s).Skip(pageIdx * pageSize).Take(pageSize);
聚合

聚合操作符Count(),Sum(),Min(),Average()等不返回一个序列,而返回一个值。

转换

Linq不只是检索数据。 它也是用于转换数据的强大工具。 通过使用 LINQ 查询,可以使用源序列作为输入,并通过多种方式对其进行修改,以创建新的输出序列。 通过排序和分组,你可以修改序列本身,而无需修改这些元素本身。 但也许 LINQ 查询最强大的功能是创建新类型
以下示例将内存中数据结构中的对象转换为 XML 元素。

var studentsToXML = new XElement("Root",
            from student in studentList
            select new 
            XElement("student",
                    new XElement("name", student.Name),
                    new XElement("age", student.Age)
    )
);
Console.WriteLine(studentsToXML);

四、并行Linq

.NET4在System.Linq名称空间中包含了一个新类ParallelEnumerable,可以分解查询的工作使其分布在多个线程上。集合序列会分成多个部分,不同的线程处理,完成后合并。这对大集合,又是多核CPU的可以提高效率

var list = (from s in studentList.AsParallel()
           where s.Age < 25
           select s.Age).Sum();
var list2 = (from s in Partitioner.Create(studentList,true).AsParallel().WithDegreeOfParallelism(8)
            where s.Age < 25
            select s.Age).Sum();

可以使用Partitioner类创建分区器,WithDegreeOfParallelism指定最大并行任务数
并行linq往往需要较多耗时使用,那应该也有取消长时间运行的任务需求。给查询添加一个WithCancellation方法,并传递一个CancellationToken令牌作为参数。该查询在单独线程中使用,主线程中触发取消命令。

var cts = new CancellationTokenSource();
new Thread(()=>
    {
        try
        {
            var sum= (from s in studentList.AsParallel().WithCancellation(cts.Token)
                      where s.Age < 25
                      select s.Age).Sum();
        }
        catch (OperationCanceledException ex)
        {
            Console.WriteLine(ex.Message);
        }
    }
).Start();
//外部动作触发取消
cts.Cancel();

五、表达式树

1、认识表达式目录树

出现在System.Linq.Expression中,就是为Linq to sql服务的。表达式树以树形数据结构表示代码,其中每一个节点都是一种表达式。它可以将我们原来直接由代码编写的逻辑存储在一个树状的结构里,然后运行的时候就去动态解析这个树。lambda表达式声明表达式目录树可以像下面这样。

Func<int, int, int> func = (m, n) => m * n + 2;// new Func<int, int, int>((m, n) => m * n + 2);
Expression<Func<int, int, int>> exp = (m, n) => m * n + 2;//lambda表达式声明表达式目录树
//Expression<Func<int, int, int>> exp1 = (m, n) =>//只能一行 不能有大括号
//    {
//        return m * n + 2;
//    };
//Queryable    //a=>a.Id>3 //表达式目录树:语法树,或者说是一种数据结构;可以被我们解析
int iResult1 = func.Invoke(12, 23);
int iResult2 = exp.Compile().Invoke(12, 23);

2、拼装表达式树

如果使用Expression类接口声明看起来会像下面这样,注意比较Lambda表达式声明和Expression类自己拼装声明的区别,最后都是需要Compile()编译后执行。

Expression<Func<int, int, int>> exp = (m, n) => m * n + 2;
ParameterExpression parameterExpression = Expression.Parameter(typeof(int), "m");
ParameterExpression parameterExpression2 = Expression.Parameter(typeof(int), "n");
var multiply = Expression.Multiply(parameterExpression, parameterExpression2);
var constant = Expression.Constant(2, typeof(int));
var add = Expression.Add(multiply, constant); Expression<Func<int, int, int>> expression =
    Expression.Lambda<Func<int, int, int>>(
        add,
        new ParameterExpression[]
        {
             parameterExpression,
             parameterExpression2
        }); int iResult1 = exp.Compile().Invoke(11, 12);
int iResult2 = expression.Compile().Invoke(11, 12);

表达式树是由表达式的主体body、表达式的参数parameters、表达式类型Type、返回类型NodeType组成。一个树可能有很多叶子节点,复杂一点的例子像下面这样

//i*j+w*x
ParameterExpression a = Expression.Parameter(typeof(int), "i");   //创建一个表达式树中的参数,作为一个节点,这里是最下层的节点
ParameterExpression b = Expression.Parameter(typeof(int), "j");
BinaryExpression r1 = Expression.Multiply(a, b);    //这里i*j,生成表达式树中的一个节点,比上面节点高一级 ParameterExpression c = Expression.Parameter(typeof(int), "w");
ParameterExpression d = Expression.Parameter(typeof(int), "x");
BinaryExpression r2 = Expression.Multiply(c, d); BinaryExpression result = Expression.Add(r1, r2);   //运算两个中级节点,产生终结点 Expression<Func<int, int, int, int, int>> lambda = Expression.Lambda<Func<int, int, int, int, int>>(result, a, b, c, d); Console.WriteLine(lambda + "");   //输出‘(i,j,w,x)=>((i*j)+(w*x))’,z对应参数b,p对应参数a Func<int, int, int, int, int> f = lambda.Compile();  //将表达式树描述的lambda表达式,编译为可执行代码,并生成该lambda表达式的委托; Console.WriteLine(f(1, 1, 1, 1) + "");  //输出结果2

上面例子形成的表达式树就像下面这样。

3、应用

最常用的地方还是查询数据时。以往我们做一个查询,根据用户输入,去数据库中查询匹配的信息,可能去想到去拼一条带where条件的sql语句,然后去执行这条sql即可。
如果无法确定需要查询的字段,当每换一个查询条件或组合多个查询条件,我们可以用表达式目录树动态的拼装起来。

还可以用来代替反射,我们知道反射有性能问题,硬编码是最快的,但不够灵活。像泛型一样,表达式树可以动态生成硬编码,缓存后以后访问调用就相当于硬编码性能。比如示例中,我们如果需要对一个类型对象转换成另一个对象。这里可以有很多方法,硬编码、反射、序列化等都可以实现,现在我们用表达式树试一下。

People people = new People()
{
    Id = 11,
    Name = "Wang",
    Age = 31
};
PeopleCopy peopleCopy = new PeopleCopy()
{
    Id = people.Id,
    Name = people.Name,
    Age = people.Age
};

硬编码像上面这样,我们用表达式目录树就是为了能够生成这种硬编码的委托

public class ExpressionMapper
{
    private static Dictionary<string, object> _Dic = new Dictionary<string, object>();     /// <summary>
    /// 字典缓存表达式树
    /// </summary>
    /// <typeparam name="TIn"></typeparam>
    /// <typeparam name="TOut"></typeparam>
    /// <param name="tIn"></param>
    /// <returns></returns>
    public static TOut Trans<TIn, TOut>(TIn tIn)
    {
        string key = string.Format("funckey_{0}_{1}", typeof(TIn).FullName, typeof(TOut).FullName);
        if (!_Dic.ContainsKey(key))
        {
            ParameterExpression parameterExpression = Expression.Parameter(typeof(TIn), "p");
            List<MemberBinding> memberBindingList = new List<MemberBinding>();
            foreach (var item in typeof(TOut).GetProperties())
            {
                MemberExpression property = Expression.Property(parameterExpression, typeof(TIn).GetProperty(item.Name));
                MemberBinding memberBinding = Expression.Bind(item, property);
                memberBindingList.Add(memberBinding);
            }
            foreach (var item in typeof(TOut).GetFields())
            {
                MemberExpression property = Expression.Field(parameterExpression, typeof(TIn).GetField(item.Name));
                MemberBinding memberBinding = Expression.Bind(item, property);
                memberBindingList.Add(memberBinding);
            }
            MemberInitExpression memberInitExpression = Expression.MemberInit(Expression.New(typeof(TOut)), memberBindingList.ToArray());
            Expression<Func<TIn, TOut>> lambda = Expression.Lambda<Func<TIn, TOut>>(memberInitExpression, new ParameterExpression[]
            {
                parameterExpression
            });
            Func<TIn, TOut> func = lambda.Compile();//拼装是一次性的
            _Dic[key] = func;
        }
        return ((Func<TIn, TOut>)_Dic[key]).Invoke(tIn);
    }
}
var result = ExpressionMapper.Trans<People, PeopleCopy>(people);

表达式和表达式树什么关系呢?首先表达式是匿名方法生成委托实例,而表达式树是一种数据结构,本身不能执行的,需要编译成sql,然后解释执行表达式树中每个节点的表达式。

六、小结

本章认识了Lambda表达式,linq查询以及相关的常用操作符,它们不仅用于筛选数据源,给数据源排序,还用于执行分区,分组,转换,连接等操作,使用并行linq可以提高大型数据集的查询效率。另一个重要概念就是表达式目录树。表达式目录树允许在运行期间构建对数据源的查询,存储在程序集中,主要用在linq to sql中,后面学习EntityFramework框架时会用到大量的表达式目录树。

debug everything

愿一觉醒来,阳光正好

而不是,一觉醒来,天都黑了

如果手机在手边,也可以关注下vx:xishaobb,互动或获取更多消息。当然这里也一直更新de,下期见,拜了个拜拜。

.NET进阶篇05-Linq、Lambda表达式的更多相关文章

  1. LinQ—Lambda表达式

    概述 本篇博客主要解说lambda表达式,在这里将它的来龙去脉,主要是从托付,匿名函数这些方面过度讲的,当然,在讲托付和匿名函数的时候,主要是从Lambda的角度出发讲的,可能它们还具有其他的一些作用 ...

  2. C#进阶之全面解析Lambda表达式

    引言 在实际的项目中遇到一个问题,我们经常在网上搜索复制粘贴,其中有些代码看着非常的简洁,比如Lambda表达式,但是一直没有去深入了解它的由来,以及具体的使用方法,所以在使用的时候比较模糊,其次,编 ...

  3. 推荐 3 篇关于 java8 Lambda表达式的文章

    State of the Lambda 这篇主要讲 Labmbda 的背景和用法 译文:深入理解Java 8 Lambda(语言篇--lambda,方法引用,目标类型和默认方法) State of t ...

  4. python进阶(1)Lambda表达式

    Lambda表达式 lambda表示的是匿名函数,不需要用def来声明,一句话就可以声明出一个函数 语法 函数名 = lambda 参数:返回值 注意点 1.函数的参数可以有多个,多个参数之间用逗号隔 ...

  5. Ext.ux.grid.feature.Searching 解析查询参数,动态产生linq lambda表达式

    上篇文章中http://www.cnblogs.com/qidian10/p/3209439.html我们介绍了如何使用Grid的查询组建,而且将查询的参数传递到了后台. 那么我们后台如何介绍参数,并 ...

  6. C#基础加强篇—委托、Lambda表达式和事件(下)

    3.事件 事件作为C#中的一种类型,为类和类的实例定义发出通知的能力,从而将事件和可执行代码捆绑在了一起.事件是对象发送的消息,以发信号通知操作的发生.操作可能是由用户交互引起的,也可能是由某些其他的 ...

  7. 在Linq to sql 和 Entity framework 中使用lambda表达式实现left join

    在Linq to sql 和 Entity framework 中使用lambda表达式实现left join 我们知道lambda表达式在Linq to sql 和 Entity framework ...

  8. Linq lamda表达式Single和First方法

      让我们来看看如何对一个整数数组使用 Single 操作符.这个整数数组的每个元素代表 2 的 1 到 10 次方.先创建此数组,然后使用 Single 操作符来检索满足 Linq Lambda表达 ...

  9. 初探Lambda表达式/Java多核编程【2】并行与组合行为

    今天又翻了一下书的目录,第一章在这之后就结束了.也就是说,这本书所涉及到的新的知识已经全部点到了. 书的其余部分就是对这几个概念做一些基础知识的补充以及更深层次的实践. 最后两个小节的内容较少,所以合 ...

随机推荐

  1. 构建之法——homework1:问题思考

    1.我看了第一章概论,1.2.4 软件工程的目标——创造“足够好”的软件,其中提到了什么是好的软件?  软件工程的一个要素就是把软件的Bug都消灭掉的过程. 提问:我们知道Bug是不可能完全消灭掉的, ...

  2. Spring MVC-从零开始-第一个控制器(不考虑命名规范)

    1.目录结构 (log4j.properties.mybatis-config.xml可忽略) 2.配置web.xml文件 <?xml version="1.0" encod ...

  3. 异步Promise及Async/Await最完整入门攻略

    一.为什么有Async/Await? 我们都知道已经有了Promise的解决方案了,为什么还要ES7提出新的Async/Await标准呢? 答案其实也显而易见:Promise虽然跳出了异步嵌套的怪圈, ...

  4. 004-python面向对象,错误,调试和测试

    ---恢复内容开始--- 1.面向对象 面向对象编程——Object Oriented Programming,简称OOP,是一种程序设计思想.OOP把对象作为程序的基本单元,一个对象包含了数据和操作 ...

  5. ng-cli新建项目

    tip:所有的命令是红色标签 , 链接为蓝色标签 使用ng-cli创建新的项目一般需要安装一些新的东西后才可以进行创建成功 1.需要先安装node.js  链接: https://nodejs.org ...

  6. 正则表达式在Java中使用

    正则表达式 定义 用一组特殊的字符来描述一组字符串的格式 用于验证字符串是否满足格式 不关心字符串的内容是否有效 1. 基本正则表达式所谓正则表达式就是使用一系列预定义的特殊字符来描述一个字符串的格式 ...

  7. Zabbix监控方案-官方最新4.4版本

    Zabbix 2019/10/12 Chenxin 参考 https://www.zabbix.com/documentation/4.0/zh/manual https://baike.baidu. ...

  8. ES6 —— entries(),keys()和values()

    ES6 提供三个新的方法 —— entries(),keys()和values() —— 用于遍历数组.它们都返回一个遍历器对象,可以用for...of循环进行遍历,唯一的区别是keys()是对键名的 ...

  9. mac安装flask

    1.1使用虚拟环境 输入以下命令可以检查系统是否安装了 virtualenv: $ virtualenv --version 大多数 Linux 发行版都提供了 virtualenv 包.例如,Ubu ...

  10. SQL创建和调用有默认值的存储过程

    先创建一个有默认值的存储过程 create procedure usp_unpass ), --有默认值的参数放到最后 as select @score go 该存储过程有2个参数 ,其中@score ...