C#复习笔记(4)--C#3:革新写代码的方式(Lambda表达式和表达式树)

Lambda表达式和表达式树

先放一张委托转换的进化图

看一看到lambda简化了委托的使用。

lambda可以隐式的转换成委托或者表达式树。转换成委托的话如下面的代码:

Func<string, int> getLength = s => s.Length;

转换成表达式树的话是下面的代码:

 Expression<Func<string, int>> getLength = s => s.Length;

委托方面的东西前面都做了详细的介绍。我们主要学习表达式树

表达式树

表达式是当今编程语言中最重要的组成成分。简单的说,表达式就是变量、数值、运算符、函数组合起来,表示一定意义的式子。例如下面这些都是(C#)的表达式:

3 //常数表达式
a //变量或参数表达式
!a //一元逻辑非表达式
a + b //二元加法表达式
Math.Sin(a) //方法调用表达式
new StringBuilder() //new 表达式myString.length//MemberAccess表达式

首先澄清一个概念:表达式。表达式是一个以;结尾的句子,如果是两句,那就不叫表达式了,比如下面不是表达式:

{
....
}

代码作为数据是一个古老的概念,.NET3. 5的表达式树提供了一种抽象的方式将一些代码表示成一个对象树。 它类似于CodeDOM, 但是在一个稍高的级别上操作。 表达式树主要用于LINQ, 本节稍后会解释表达式树对于整个LINQ的重要性。

System.Linq.Expressions命名空间包含了代表表达式的各个类, 它们都继承自Expression,一个抽象的主要包含一些静态工厂方法的类, 这些方法用于创建其他表达式类的实例。 然而,Expression 类也包括两个属性。

  • Type属性代表表达式求值后的.NET类型, 可把它 视为一个返回类型。 例如,如果一个表达式要获取一个字符串的Length属性, 该表达式的类型就是int。
  • NodeType属性返回所代表的表达式的种类。 它是ExpressionType枚举的成员, 包括LessThan、Multiply和Invoke等。 仍然使用上面的例子,对于myString. Length 这个属性访问来说, 其节点类型是MemberAccess。
 static void Main(string[] args)
        {
            Expression firstArg = Expression.Constant(2);
            Expression secondArg = Expression.Constant(3);
            BinaryExpression add = Expression.Add(firstArg, secondArg);
            Console.WriteLine(add);
            Console.ReadKey();
        }

上面的结果最终会输出(2+3).这意味着这些表达式树类覆盖了ToString来产生可读的输出。

就像代码中所做的,首先创建的是叶表达式,然后自下而上的去创建这个完整的表达式。这是由“ 表达式不易变” 这一事实决定的——创建好表达式后, 它就永远不会改变。 这样就可以随心所欲地缓存和重用表达式。

LambdaExpression是从Expression派生的类型之一。 泛型类Expression<TDelegate>又是从LambdaExpression派生的。

Expression和Expression<TDelegate> 类的区别在于, 泛型类以静态类型的方式标识了它是什么种类的表达式,也就是说,它确定了返回类型和参数。 很明显, 这是用TDelegate类型参数来表示的, 它必须是一个委托类型。 例如, 假设我们的简单加法表达式就是一个 不获取任何参数, 并返回整数的委托。 与之匹配的签名就是Func<int>, 所以可以使用一个Expression <Func<int>>, 以静态类型的方式表示该表达式。 我们用Expression.Lambda 方法来完成这件事。 该方法有许多重载版本——我们的例子使用的是泛型方法, 它用一个类型参数来指定我们想要表示的委托的类型。

 static void Main(string[] args)
        {

            Expression firstArg = Expression.Constant(2);
            Expression secondArg = Expression.Constant(3);
            BinaryExpression add = Expression.Add(firstArg, secondArg);
            Func<int> lambda = Expression.Lambda<Func<int>>(add).Compile();
            Console.WriteLine(lambda());
            Console.ReadKey();
        }

Expression.Lambda有泛型的重载,指示表达式可以转换为一个类型实参为Func<int>的Expression,然后,可以通过Compile方法来将表达式树编译成委托。

印象。我们在程序中创建了一些逻辑块(比如Expression firstArg = Expression.Constant(2);), 将其表示成普通对象, 然后要求框架将所有的东西都编译成可以执行的“真实” 的代码。 你或许永远都不需要真正以这种方式使用表达式树, 甚至永远都不需要在程序中构造 它们,但它提供了相当有用的背景知识,可以帮助你理解LINQ是怎样工作的。

上面介绍的是将表达式树编译成lambda,下面介绍的是----

将lambda转换成表达式树

我们知道,Lambda表达式能显式或隐式地转换成恰当的委托实例。 然而, 这并非唯一能进行的转换。 还可以要求编译器通过你的Lambda 表达式构建一个表达式树, 在执行时创建Expression<TDelegate> 的一个实例。 例如,下面展示了用一种精简得多的方式 创建“返回 5” 的表达式, 然后编译这个表达式, 并调用编译得到的委托。

 static void Main(string[] args)
        {

            Expression<Func<int>> return5 = () => 5;
            Func<int> lambda = return5.Compile();
            Console.WriteLine(lambda());
            Console.ReadKey();
        }

一些限制:

  • 并非**所有** Lambda表达式都能转换成表达式树。 不能将带有一个语句块(即使只有一个return语句) 的Lambda 转换成表达式树—— 只有对单个表达式进行求值的Lambda才可以。
  • 表达式中还不能包含赋值操作,因为在表达式树中表示不了这种操作。 尽管.NET4 扩展了表达式树的功能, 但只能转换单一表达式这一限制仍然有效。
  • 还有一些其他的限制,不过很少见,你会在编译错误的时候得到编译器的提示

更复杂的例子:

 static void Main(string[] args)
        {

            Expression<Func<string, string, bool>> expression = (x, y) => x.StartsWith(y);
            var lambda = expression.Compile();
            Console.WriteLine(lambda("first that i did","first"));//true
            Console.ReadKey();
        }

上面这个例子如果用表达式树来做的话是非常复杂的:

 static void Main(string[] args)
        {

            MethodInfo method = typeof(string).GetMethod("StartsWith", new []{typeof(string)});//①构造这个方法调用的各个部件
            var target = Expression.Parameter(typeof(string), "x");
            var methodArg = Expression.Parameter(typeof(string), "y");
            Expression[] methodArgs =new[] {methodArg};
            Expression call = Expression.Call(target, method, methodArgs);//②从以上部件构建callexpression
            var lambdaParameters = new[]{target, methodArg};//③将callexpression转化成lambda
            var lambda = Expression.Lambda<Func<string,string,bool>>(call,lambdaParameters);
            var compiled = lambda.Compile();
            Console.WriteLine(compiled("first","second"));//false
            Console.WriteLine(compiled("first", "fir"));//true
            Console.ReadKey();
        }

首先感谢编译器能够让lambda隐式转化成表达式树!

唯一的好处是它确实更清晰地展示树中涉及的东西以及参数是如何绑定的。 为了构造最终的方法调用表达式, 我们需要知道方法调用的几个部件①, 其中包括: 方法的目标(也就是调用StartsWith的字符串);方法本身( MethodInfo);参数列表(本例只有一个参数)。在本例中, 方法的目标和参数恰好都是传递给表达式的参数, 但它们完全可能是其他表达式类型, 如常量、其他方法调用的结果、属性的求值结果, 等等。 将方法调用构造成一个表达式之后 ?, 接着需要把它转换成Lambda表达式 ?, 并绑定参数。 我们重用 了作为方法调用(部件)信息而创建的参数表达式的值(ParameterExpression): 创建Lambda表达式时指定的参数顺序就是最终调用委托时使用的参数顺序。

这是编译好之后的表达式树。

位于linq核心的表达式树

没有lambda表达式,表达式树几乎没有任何价值。从一定程度上说,没有表达式树,lambda也就没那么有用了。LINQ在C#的全部体现就包括lambda、表达式树和扩展方法这三部分。

长期以来, 我们要么能在编译时进行很好的检查, 要么能指示另一个平台运行一些代码, 这些指示一般表示成文本(如SQL查询)。 但是,鱼和熊掌不可兼得(这个却是lambda的优点,既能转化成委托,在进程内配合迭代器进行处理集合序列,又能编译成表达式树,以供其他提供器翻译成另一个平台上的语言,比如sql)。 Lambda表达式提供 了编译时检查的能力, 而表达式树可以将执行模型从你所需的逻辑中提取出来。 将两者合并到一起之后, 鱼和熊掌就能兼得 了—— 当然是在一个合理的范畴之内。“ 进程外” LINQ提供器的中心思想在于, 我们可以从一个熟悉的源语言(如 C#) 生成一个表达式树, 将结果作为一个中间格式, 再将其转换成目标平台上的本地语言, 比如SQL。 某些时候, 你更多地会遇到一个本机API, 而不是一种简单的本机语言。 例如, 这个API可能根据表达式所表示的 内容来调用不同的Web服务。下图展示LINQ to Objects 和 LINQ to SQL 的 不同 路径。

除了LINQ,表达式树也可以用在别的地方

1、我们在以后的内容中讨论C#动态类型 时, 将看到更多关于动态语言运行时的内容。 表达式树是其架构的核心部分。 它们具有三个特点对DLR特别有吸引力:

  • 它们是不易变的, 因此可以安全地缓存;
  • 它们是可组合的, 因此可以在简单的块中构建出复杂的行为;
  • 它们 可以编译为委托, 后者可以像平常那样进一步JIT 编译为本地代码。

2、可以放心地对成员的引用进行重构

以后C#会推出一个infoof的操作符,但具体是干啥的还不知道,这里先做标记。以后来补充。

3、其他。。。。

这个回头再来看一下书上的介绍吧。貌似没有用到过

类型推断和重载决策的改变

C#3中lambda表达式的加入使得原先的类型推断和重载决策为了新的环境而做了改变。

原文地址:https://www.cnblogs.com/pangjianxin/p/8669313.html

时间: 2024-10-28 06:36:39

C#复习笔记(4)--C#3:革新写代码的方式(Lambda表达式和表达式树)的相关文章

C#复习笔记(4)--C#3:革新写代码的方式(用智能的编译器来防错)

用智能的编译器来防错 本章的主要内容: 自动实现的属性:编写由字段直接支持的简单属性, 不再显得臃肿不堪: 隐式类型的局部变量:根据初始值推断类型,简化局部变量的声明: 对象和集合初始化程序:用一个表达式就能创建和初始化对象: 隐式类型的数组:根据内容推断数组的类型,从而简化数组的创建过程: 匿名类型:允许创建新的临时类型来包含简单的属性: 自动实现的属性 这个特性简单的我都不想描述,但是为了保持内容的完整性,放一张图: 和匿名方法还有迭代器一样,它在编译器的帮助下会生成一个后备字段. 自动实现

C#复习笔记(4)--C#3:革新写代码的方式(扩展方法)

扩展方法 扩展方法有以下几个需求: 你想为一个类型添加一些 成员: 你不需要为类型的实例添加任何更多的数据: 你不能改变类型本身, 因为是别人的代码. 对于C#1和C#2中的静态方法,扩展方法是一种更优雅的解决方案. 语法 并不是任何方法都能作为扩展方法使用-- 它必须具有以下特征: 它必须在一个非嵌套的. 非泛型的静态类中( 所以必须是一 个静态方法): 它至少要有 一个参数: 第一个参数必须附加 this 关键字作为前缀: 第一个参数不能有其他任何修饰 符(比如out或ref): 第一个参数

C#复习笔记(4)--C#3:革新写代码的方式(查询表达式和LINQ to object(下))

查询表达式和LINQ to object(下) 接下来我们要研究的大部分都会涉及到透明标识符 let子句和透明标识符 let子句不过是引入了一个新的范围变量.他的值是基于其他范围变量的.let 标识符=表达式; 首先展示一个不适用let操作符来使用的按用户名称长度来排序: ... var queryWithoutLet = from user in SampleData.AllUsers orderby user.Name.Length select user; foreach (User us

写出优美代码的方式,两个习惯:一步到位VS迭代优化

最近把手头这个安卓APP的所有事务性方法都写完了,有了以下体会,新手体会,老鸟轻拍 想写成优美代码的人一般都会有这样的想法: 一定要在写每一句代码,写每一个方法,构造每一个类的时候,都要记得优化:解耦以复用,拆分方法以复用,使用循环减少冗余,限制循环次数减少无效操作,等等.. 这个想法一定没有错,但很多时候往往会是这样的情况: 当功能一复杂,比如你已经分解了几个方法,比如你已经使用了几层循环(有点过分...),比如在多线程中 你经常无法一步到位地完成那么多优化 这往往造成你写一句代码会思考很久

程序员如何像写代码一样找女朋友

在程序员的世界里,妹子是稀有动物,女神就更是凤毛麟角了,大部分程序员由于经常面对电脑,缺乏与人的沟通交流,加上软件行业的工作特殊性,因此找女朋友更是难上加难.那么,程序员如何用自己的方法去追求心仪的女生呢?有这个冲动的朋友请继续看下去. 1.需求分析 根据自己的性格特点.经济实力,合理定位:适合自己的女性范围,也就是软件工程里常谈到:需求分析.自己最想找什么样的女孩,譬如:身高在什么范 围,年龄在什么范围,学历在什么范围,相貌有什么要求,对性格有什么偏好,喜静还是偏活泼.你越能更多了解自己,知道

安卓开发复习笔记——Fragment+FragmentTabHost组件(实现新浪微博底部菜单)

记得之前写过2篇关于底部菜单的实现,由于使用的是过时的TabHost类,虽然一样可以实现我们想要的效果,但作为学习,还是需要来了解下这个新引入类FragmentTabHost 之前2篇文章的链接: 安卓开发复习笔记——TabHost组件(一)(实现底部菜单导航) 安卓开发复习笔记——TabHost组件(二)(实现底部菜单导航) 关于Fragment类在之前的安卓开发复习笔记——Fragment+ViewPager组件(高仿微信界面)也介绍过,这里就不再重复阐述了. 国际惯例,先来张效果图: 下面

安卓开发复习笔记——Fragment+ViewPager组件(高仿微信界面)

什么是ViewPager? 关于ViewPager的介绍和使用,在之前我写过一篇相关的文章<安卓开发复习笔记——ViewPager组件(仿微信引导界面)>,不清楚的朋友可以看看,这里就不再重复. 什么是Fragment? Fragment是Android3.0后新增的概念,Fragment名为碎片,不过却和Activity十分相似,具有自己的生命周期,它是用来描述一些行为或一部分用户界面在一个Activity中,我们可以合并多个Fragment在一个单独的activity中建立多个UI面板,或

[Java基础] Java线程复习笔记

先说说线程和进程,现代操作系统几乎无一例外地采用进程的概念,进程之间基本上可以认为是相互独立的,共享的资源非常少.线程可以认为是轻量级的进 程,充分地利用线程可以使得同一个进程中执行多种任务.Java是第一个在语言层面就支持线程操作的主流编程语言.和进程类似,线程也是各自独立的,有自 己的栈,自己的局部变量,自己的程序执行并行路径,但线程的独立性又没有进程那么强,它们共享内存,文件资源,以及其他进程层面的状态等.同一个进程内的 多个线程共享同样的内存空间,这也就意味着这些线程可以访问同样的变量和

oracle从入门到精通复习笔记续集之PL/SQL(轻量版)

复习内容: PL/SQL的基本语法.记录类型.流程控制.游标的使用. 异常处理机制.存储函数/存储过程.触发器. 为方便大家跟着我的笔记练习,为此提供数据库表文件给大家下载:点我下载 为了要有输出的结果,在写PL/SQL程序前都在先运行这一句:set serveroutput on结构:declare--声明变量.类型.游标begin--程序的执行部分(类似于java里的main()方法)exception--针对begin块中出现的异常,提供处理的机制--when...then...--whe