博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
C# in depth (第九章 Lambda表达式和表达式树)
阅读量:5253 次
发布时间:2019-06-14

本文共 11516 字,大约阅读时间需要 38 分钟。

LINQ 的基本功能就是创建操作管道,以及这些操作需要的任何状态。 这些操作表示了各种关于数据的逻辑:如何过滤,如何排序以及如何将不同的数据源连接在一起。

9.1 作为委托的Lambda表达式

  • 从许多方面,Lambda表达式都可以看作是C#2.0的匿名方法的一种演变。匿名方法能做的几乎一切事情都可以用Lambda表达式来完成。
  • 表达式的类型本身并非委托类型,但它可以通过多种方式隐式或显示地转换成一个委托实例。

9.1.1 准备工作: Func<...>委托类型简介

如果我们需要获取一个string参数,并返回一个int, 所以我们将使用 Func<string, int>

 

9.1.2 第一次转换成Lambda表达式

用匿名方法来创建委托

class SimpleAnonymousMethod    {        static void Main()        {            Func
returnLength; returnLength = delegate(string text) { return text.Length; }; Console.WriteLine(returnLength("Hello")); } }

冗长的第一个Lambda表达式,和匿名方法相似  (可以将=>读成 goes to )

class FirstLambdaExpression    {        static void Main()        {            Func
returnLength; returnLength = (string text) => { return text.Length; }; Console.WriteLine(returnLength("Hello")); } }

 

9.1.3 用单一表达式作为主体

(string text) => { return text.Length; };可以转变为 (string text) =>  text.Length;

 

9.1.4 隐式类型的参数列表

编译器大多数时候都能猜出参数类型,不需要你显示声明它们。在这些情况下,可以将Lambda表达式写成:

(隐式类型的参数列表)=>表达式

 

(string text) =>  text.Length; 可以转换为(text) =>text.Length;

 

9.1.5 单一参数的快捷与法

最终可以把那个小括号也去掉

class FirstLambdaExpression    {        static void Main()        {            Func
returnLength; returnLength = (string text) => { return text.Length; }; Console.WriteLine(returnLength("Hello")); } }

 

9.2 使用List<T>和事件的简单例子

9.2.1 列表的过滤,排序和操作

class FilmFilteringAndSorting    {        class Film        {            public string Name { get; set; }            public int Year { get; set; }        }        static void Main()        {            var films = new List
{ new Film {Name="Jaws", Year=1975}, new Film {Name="Singing in the Rain", Year=1952}, new Film {Name="Some like it Hot", Year=1959}, new Film {Name="The Wizard of Oz", Year=1939}, new Film {Name="It's a Wonderful Life", Year=1946}, new Film {Name="American Beauty", Year=1999}, new Film {Name="High Fidelity", Year=2000}, new Film {Name="The Usual Suspects", Year=1995} }; Action
print = film => Console.WriteLine("Name={0}, Year={1}", film.Name, film.Year); // Note: extra lines added for clarity when running Console.WriteLine("All films"); films.ForEach(print); Console.WriteLine(); Console.WriteLine("Oldies"); films.FindAll(film => film.Year < 1960) .ForEach(print); Console.WriteLine(); Console.WriteLine("Sorted"); films.Sort((f1, f2) => f1.Name.CompareTo(f2.Name)); films.ForEach(print); } }

 9.2.2 在事件处理程序中进行记录

class EventLogging    {        static void Log(string title, object sender, EventArgs e)        {            Console.WriteLine("Event: {0}", title);            Console.WriteLine("  Sender: {0}", sender);            Console.WriteLine("  Arguments: {0}", e.GetType());            foreach (PropertyDescriptor prop in                     TypeDescriptor.GetProperties(e))            {                string name = prop.DisplayName;                object value = prop.GetValue(e);                Console.WriteLine("    {0}={1}", name, value);            }        }        static void Main()        {            Button button = new Button();            button.Text = "Click me";            button.Click += (src, e) => Log("Click", src, e);            button.KeyPress += (src, e) => Log("KeyPress", src, e);            button.MouseClick += (src, e) => Log("MouseClick", src, e);            Form form = new Form();            form.AutoSize = true;            form.Controls.Add(button);            Application.Run(form);        }    }

9.3 表达式树

9.3.1 以编程方式构建表达式树

  • 表达式树是对象构成的树,数中每个节点本身都是一个表达式。不同的表达式类型代表能在代码中执行的不同操作:二元操作(例如加法),一元操作(例如获取一个数组的长度),方法调用,构造函数调用,等等。
  • System.Linq.Expressions命名空间包含了代表表达式的各个类,它们都继承自Expression,一个抽象的主要包含一些静态工厂方法的类,这些方法用于创建其他表达式的实例。然而,Expression类也包括两个属性。
  1. Type属性代表表达式求值后的.Net类型,可把它视为一个返回类型。例如,如果一个表达式要获取一个字符串的Length属性,该表达式的类型就是int。
  2. NodeType属性返回所代表的表达式的种类。它是ExpressionType枚举的成员,包括LessThan, Multipy和Invoke等。 仍然使用上面的例子,对于mystring.Length这个属性访问来说,其节点类型是MemberAccess。
class FirstExpressionTree    {        static void Main()        {            Expression firstArg = Expression.Constant(2);            Expression secondArg = Expression.Constant(3);            Expression add = Expression.Add(firstArg, secondArg);                       Console.WriteLine(add);        }    }

 

 

add

BinaryExpression

NodeType =Add

Type= System.Int32

 

firsttArg

ConstantExpression

NodeType=Constant

Type=System.Int32

Value=2

 

secondArg

ConstantExpression

NodeType=Constant

Type=System.Int32

Value=3

 9.3.2 将表达式树编译成委托

(继承关系)

  Expression  
LambdaExpression BinaryExpression 其他类型(ConstantExpression)
Expression<TDelegate>    
  • Expression 和 Expression<TDelegate>类的区别在于,泛型类以静态类型的方式标示了它是什么种类的表达式,也就是说,它确定了返回类型的参数。很明显TDelegate必须是一个委托
  • 那么,这样做的意义何在呢?LambdaExpression有一个Compile方法能创建恰当类型的委托。Expression<TDelegate>也有一个同名的方法,但它静态类型化后返回TDelegate类型的委托。该委托现在可以采用普通方式执行,就好像它是用一个普通方法或者其他方式来创建的一样。
class CompiledExpressionTree    {        static void Main()        {            Expression firstArg = Expression.Constant(2);            Expression secondArg = Expression.Constant(3);            Expression add = Expression.Add(firstArg, secondArg);            Func
compiled = Expression.Lambda
>(add).Compile(); Console.WriteLine(compiled()); } }

 9.3.3  将C# Lambda表达式转换成表达式树

class LambdaExpressionToExpressionTree    {        static void Main()        {            Expression
> return5 = () => 5;//编译器可以自动将lambda表达式转换成表达式树。 Func
compiled = return5.Compile(); Console.WriteLine(compiled()); } }
  •  有一些限制:
  1. 不能将带有一个语句块(即使只有一个return语句)的lambda表达式转换成表达式树

表达式中还不能包含赋值操作,因为在表达式树中表示不了这种操作。

class LambdaExpressionWithParametersToExpressionTree    {        static void Main()        {            Expression
> expression = (x, y) => x.StartsWith(y); var compiled = expression.Compile(); Console.WriteLine(compiled("First", "Second")); Console.WriteLine(compiled("First", "Fir")); } }

 用代码来构造一个方法调用表达式树

class MethodCallExpressionTree    {        static void Main()        {            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 };            //1.以上为构造方法调用的各个部件            Expression call = Expression.Call(target, method, methodArgs);            //2.从以上部件创建CallExpression var lambdaParameters = new[] { target, methodArg };            var lambda = Expression.Lambda
>(call, lambdaParameters); var compiled = lambda.Compile(); //3.以上将Call转换成Lambda表达式 Console.WriteLine(compiled("First", "Second")); Console.WriteLine(compiled("First", "Fir")); } }
  1.  为了构造最终的方法调用表达式,我们需要知道方法调用的几个部件(1.) 其中包括: 方法的目标(也就是调用StartsWith的字符串);方法本身(MethodInfo);参数列表(本例只有一个参数)。
  2. 将方法调用构造成一个表达式之后(2.),接着需要把它转换成Lambda表达式(3.),并绑定参数。

     P.217 图9-4

9.3.4 位于LINQ核心的表达式树

  • Lambda表达式提供了编译时检查的能力,而表达式树可以将执行模型从你所需的逻辑中提取出来。
  • LINQ提供器的中心思想在于,我们可以从一个熟悉的源语言(如C#)生成一个表达式树,将结果作为一个中间格式,再将其转换成目标平台上的本地语言。比如SQL。
 

编译时 

 
含有Lambada表达式的C#查询代码 含有Lambada表达式的C#查询代码

                  (C#编译器)

(C#编译器)
使用委托的IL 使用表达式树的IL

 

 执行时

委托代码直接在CLR中执行

                    (LINQ to SQL 提供器)
              动态SQL
                         (在数据库处执行,并取回结果)
查询结果    查询结果
 LINQ to Objects LINQ to SQL 

                               无论LINQ to Objects还是LINQ to SQL都是始于C#代码,结束于查询结果。表达式树提供了远程执行代码的能力。

  • 编译器并不能做所有的检查 :例如:索然可以将string.StartWith调用转换成类似的SQL表达式,但string.IsInterned的调用在数据库环境中是没有意义的。表达式树确保了大量编译时安全性,但编译器只能检查以确保Lambda表达式能转换成一个有效的表达式树,它不能保证表达式树最后的使用是否合适。

 

9.3.5 LINQ之外的表达式树

  1. 优化动态语言运行时
  2. 可以放心地对成员的引用进行重构
  3. 更简单的反射

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

9.4.1 改变的起因

需要新的类型推断规则的例子:

class ConversionWithTypeInference    {        static void PrintConvertedValue
(TInput input, Converter
converter) { Console.WriteLine(converter(input)); } static void Main() { PrintConvertedValue("I'm a string", x => x.Length); } }
  • 第一个实参明显是个字符串,但第二个呢?是个Lambda表达式,所以需要把它转换成一个Converter<TInput, TOutput>,而那意味着要知道TInput和TOutput的类型。所以如果还是沿用C#2.0的规则,代码清单9-11的代码就会编译失败。

9.4.2 推断匿名函数的返回类型

以下代码清单展示了貌似能编译,但不符合C#2类型推断规则的示例代码 (其实是可以编译的???)

class ReturnTypeInference    {        delegate T MyFunc
(); static void WriteResult
(MyFunc
function) { Console.WriteLine(function()); } static void Main() { WriteResult(delegate { return 5; }); } }

代码清单9-13 根据一天当中的时间来选择返回int或object

class ReturnTypeInferenceWithMultipleReturns    {        delegate T MyFunc
(); static void WriteResult
(MyFunc
function) { Console.WriteLine(function()); } static void Main() { WriteResult(delegate { if (DateTime.Now.Hour < 12) { return 10; } else { return new object(); } }); } }
  • 在这种情况下,编译器采用和处理隐式类型的数组时相同的逻辑来确定返回类型。int到object存在一个隐式转换,但object到int就不存在了。所以object被推断为返回类型。

9.4.3 分两个阶段进行的类型推断

综合来自多个实参的信息,灵活地进行推断

class MultipleArgumentInference    {        static void PrintType
(T first, T second) { Console.WriteLine(typeof(T)); } static void Main() { PrintType(1, new object()); } }
  • 和上一个例子一样,这里T会被推断为Object
  • 类型推断现在是分两个阶段进行的。
  1. 第一个阶段处理的是“普通“的实参,其类型是一开始便知道的。
  2. 第二个阶段是推断隐式类型的Lambda表达式和方法组的类型,其思想是根据我们迄今为止拼凑起来的信息,判断是否足够推断出Lambda表达式(或方法组)的参数类型。

图9-7用流程图展示了这一过程。(P.225)

class MultiStageInference    {        static void ConvertTwice
(TInput input, Converter
firstConversion, Converter
secondConversion) { TMiddle middle = firstConversion(input); TOutput output = secondConversion(middle); Console.WriteLine(output); } static void Main() { ConvertTwice("Another string", text => text.Length, length => Math.Sqrt(length)); } }

9.4.4 选择正确的被重载的方法。

  • void Write(int x)  void Write(double y)  如果 Write(1) 会调用 Write(int x) 这个规则叫”更好的转换“规则
class OverloadingByDelegateReturnType    {        static void Execute(Func
action) { Console.WriteLine("action returns an int: " + action()); } static void Execute(Func
action) { Console.WriteLine("action returns a double: " + action()); } static void Main() { Execute(() => 1); //会调用第一个Execute函数 } }

 9.4.5 类型推断和重载决策

9.5 小结

  • 我们知道Lambda表达式并非仅仅是创建委托的一种更精简的语法。它们能转换成表达式树,然后可由其他代码处理,从而在不同的环境中执行等价的行为。如果没有这项功能,LINQ就仅限于进程内查询。

转载于:https://www.cnblogs.com/leonhart/p/4764027.html

你可能感兴趣的文章
Linux SPI总线和设备驱动架构之四:SPI数据传输的队列化
查看>>
SIGPIPE并产生一个信号处理
查看>>
CentOS
查看>>
Linux pipe函数
查看>>
java equals 小记
查看>>
爬虫-通用代码框架
查看>>
2019春 软件工程实践 助教总结
查看>>
YUV 格式的视频呈现
查看>>
Android弹出框的学习
查看>>
现代程序设计 作业1
查看>>
在android开发中添加外挂字体
查看>>
Zerver是一个C#开发的Nginx+PHP+Mysql+memcached+redis绿色集成开发环境
查看>>
多线程实现资源共享的问题学习与总结
查看>>
Learning-Python【26】:反射及内置方法
查看>>
torch教程[1]用numpy实现三层全连接神经网络
查看>>
java实现哈弗曼树
查看>>
转:Web 测试的创作与调试技术
查看>>
python学习笔记3-列表
查看>>
程序的静态链接,动态链接和装载 (补充)
查看>>
关于本博客说明
查看>>