Keep on going never give up.

Let's Go

C# 学习笔记(58)Lambda表达式

C#Lonely2019-10-03 15:50:038次0条

Lambda表达式简介

Lambda表达式可以理解为一个匿名方法,它可以包含表达式和语句,并且用于创建委托或转换为表达式树。在使用Lambda表达式时,都会使用“=>”运算符(读作“goes to”),该运算符的左边时匿名方法的输入参数,右边是表达式或语句块。


Lambda表达式的演变过程

大家可以认为匿名方法就是Lambda表达式的“前世”,而Lambda则是匿名方法的投胎转世,种种因果都与Lambda表达式的演变过程有关。下面代码演示Lambda表达式从匿名方法一路演变而来的过程。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace ConsoleApp
{
    class Program
    {
        static void Main(string[] args)
        {
            //Lambda表达式的演变过程
            //下面是C# 1.0中创建委托实例的代码
            Func<string, int> delegateTest_1 = new Func<string, int>(Callback);

            //C# 2.0中用匿名方法来创建委托实例,此时就不需要去额外定义回调方法Callback了
            Func<string, int> delegateTest_2 = delegate (string text) { return text.Length; };

            //C# 3.0中使用Lambda表达式创建委托实例
            Func<string, int> delegateTest_3 = (string text) => text.Length;

            //可以省略参数类型string,从而将代码再次简化
            Func<string, int> delegateTest_4 = (text) => text.Length;

            //再次简化,此时圆括号()也可以省略掉
            Func<string, int> delegateTest_5 = text => text.Length;

            //调用委托
            Console.WriteLine("字符串长度为:{0}",delegateTest_5("你好,世界!"));
            Console.ReadKey();

        }

        //回调方法
        private static int Callback(string text)
        {
            return text.Length;
        }

    }
}

以上代码演示了Lambda表达式的如何由匿名方法演变而来的过程,由此看出Lambda表达式十分简洁。


Lambda表达式的使用

在实际开发过程中,委托的用途莫过于订阅事件了,为了加深大家对Lambda表达式的理解,这里选择演示用Lambda表达式去订阅事件。

下面给出的是C# 3.0之前的订阅代码,以形成对比,代码演示如下:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
//添加对System.Windows.Forms.dll的引用并加入命名空间
using System.Windows.Forms;

namespace ConsoleApp
{
    class Program
    {
        static void Main(string[] args)
        {
            Button btn = new Button();
            btn.Text = "按钮";

            //C# 2.0中使用匿名方法来订阅事件
            btn.Click += delegate (object sender, EventArgs e)
            {
                ReportEvent("Click事件",sender,e);
            };

            btn.KeyPress += delegate (object sender, KeyPressEventArgs e)
            {
                ReportEvent("KeyPress事件", sender, e);
            };

            //在C# 3.0之前,初始化对象会使用以下代码
            Form frm = new Form();
            frm.Text = "控制台中创建出来的窗体";
            frm.AutoSize = true;
            frm.Controls.Add(btn);
            //运行窗体
            Application.Run(frm);

        }

        //记录事件的回调方法
        private static void ReportEvent(string title,object sender,EventArgs e)
        {
            Console.WriteLine("发生事件:{0}",title);
            Console.WriteLine("发生事件对象:{0}",sender);
            Console.WriteLine("发生事件参数:{0}\n\n",e.GetType());
        }

    }
}

以上是C# 3.0之前的实现方式,运行结果如下所示:

image.png


看完了C# 3.0之前的实现代码,我们再来用C# 3.0特性实现同样的效果,C# 3.0的实现代码如下所示:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
//添加对System.Windows.Forms.dll的引用并加入命名空间
using System.Windows.Forms;

namespace ConsoleApp
{
    class Program
    {
        static void Main(string[] args)
        {
            Button btn = new Button() { Text = "按钮" };

            //C# 3.0采用Lambda表达式的方式来订阅事件
            btn.Click += (sender, e) => ReportEvent("Click事件", sender, e);
            btn.KeyPress += (sender, e) => ReportEvent("KeyPress事件", sender, e); ;

            //在C# 3.0中使用对象集合初始化器
            Form frm = new Form()
            {
                Text = "控制台中创建出来的窗体",
                AutoSize = true,
                Controls = { btn }
            };

            //运行窗体
            Application.Run(frm);

        }

        //记录事件的回调方法
        private static void ReportEvent(string title,object sender,EventArgs e)
        {
            Console.WriteLine("发生事件:{0}",title);
            Console.WriteLine("发生事件对象:{0}",sender);
            Console.WriteLine("发生事件参数:{0}\n\n",e.GetType());
        }

    }
}

以上代码可以看出,使用C# 3.0的对象集合初始化器和Lambda表达式后,代码确实简洁不少。这里,委托可以用Lambda表达式来实例化,去除了多余大括号代码。Lambda表达式的使用可以明显减少代码的书写量,从而有利于开发人员更好的维护代码,理清程序的结构。


表达式树

Lambda表达式除了可以用来创建委托外,还可以转换成表达式树。表达式树(或称“表达式目录树”)是用来表示Lambda表达式逻辑的一种数据结构,它将代码表示成一个对象树,而非可执行的代码。


动态构造一个表达式树

代码示例:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
//引入Expressions<TDelegate>类的命名空间
using System.Linq.Expressions;

namespace ConsoleApp
{
    class Program
    {
        static void Main(string[] args)
        {
            // 构造“a+b”的表达式结构

            //表达式参数
            //ParameterExpression 表示一个命名的参数表达式。
            //Expression 提供一种基类,表示表达式树节点的类派生自该基类。 它还包含用来创建各种节点类型的 static(在 Visual Basic 中为 Shared)工厂方法。这是一个 abstract 类
            //Expression.Parameter(Type type, string name) 创建一个 ParameterExpression 节点,该节点可用于标识表达式树中的参数或变量。
            //参数:
            //type: 参数或变量的类型。 name:仅用于调试或打印目的的参数或变量的名称。
            ParameterExpression a = Expression.Parameter(typeof(int),"a");

            ParameterExpression b = Expression.Parameter(typeof(int), "b");

            //表达式树的主体部分
            //BinaryExpression 表示具有二进制运算符的表达式。
            //Expression.Add(Expression left, Expression right) 创建一个表示不进行溢出检查的算术加法运算的 BinaryExpression。
            BinaryExpression be = Expression.Add(a,b);

            //构造表达式树
            //Expression<> 将强类型化的 Lambda 表达式表示为表达式树形式的数据结构。 此类不能被继承。
            //Expression.Lambda<>() 创建一个在编译时委托类型已知的 Expressions<TDelegate>
            Expression<Func<int, int, int>> expressionTree = Expression.Lambda<Func<int, int, int>>(be,a,b);

            //分析树结构,获取表达式树的主体部分
            BinaryExpression body = (BinaryExpression)expressionTree.Body;

            //左节点,每一个节点本身就是一个表达式对象
            ParameterExpression left = (ParameterExpression)body.Left;

            //右节点
            ParameterExpression right = (ParameterExpression)body.Right;

            Console.WriteLine("表达式结构为:{0}",expressionTree.Body);
            Console.WriteLine();
            Console.WriteLine("表达式左节点为:{0},节点类型为:{1}",left.Name,left.Type);
            Console.WriteLine();
            Console.WriteLine("表达式右节点为:{0},节点类型为:{1}", right.Name, right.Type);
            Console.ReadKey();

        }


    }
}

以上代码演示了通过一个表达式动态地构造表达式树对象,然后输出表达式树的结构、主体、和左右节点的过程,运行结果如下所示:

image.png

前面代码所构造的表达式树结构可以用下图来更形象地表示,由图可以看出表达式树也是一种树形数据结构,该数据结构可以很好地描述Lambda表达式的逻辑。

image.png


通过Lambda表达式来构造表达式树

前面代码演示了动态地构造表达式树的方法,除此之外,还可以直接使用Lambda表达式来构造表达式树,具体构造过程如下所示:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
//引入Expressions<TDelegate>类的命名空间
using System.Linq.Expressions;

namespace ConsoleApp
{
    class Program
    {
        static void Main(string[] args)
        {
            // 构造“a+b”的表达式结构

            //将Lambda表达式构造成表达式树
            Expression<Func<int, int, int>> expressionTree = (a, b) => a + b;

            //获得表达式树参数
            Console.WriteLine("参数1:{0},参数2:{1}",expressionTree.Parameters[0],expressionTree.Parameters[1]);
            Console.WriteLine();

            //获取表达式树的主体部分
            BinaryExpression body = (BinaryExpression)expressionTree.Body;

            //左节点,每一个节点本身就是一个表达式对象
            ParameterExpression left = (ParameterExpression)body.Left;

            //右节点
            ParameterExpression right = (ParameterExpression)body.Right;

            Console.WriteLine("表达式结构为:{0}",expressionTree.Body);
            Console.WriteLine();
            Console.WriteLine("表达式左节点为:{0},节点类型为:{1}",left.Name,left.Type);
            Console.WriteLine();
            Console.WriteLine("表达式右节点为:{0},节点类型为:{1}", right.Name, right.Type);
            Console.ReadKey();

        }


    }
}

从以上代码可以看出,通过Lambda表达式来构造表达式树的过程非常简单,只需要把Lambda表达式赋给一个表达式树变量就可以了。构造完一个表达式树后,由于表达式对象并不是可执行代码,它只是一个树形数据结构,所以需要在代码中再解析树结果,分别获得树的参数和左右节点,然后输出,运行结果如下所示:

image.png


如何把表达式树转换成可执行代码

看完前面的代码,你肯定会问:“表达式树是一种树形数据结构,但最终还是需要得到代码的执行结果的,有没有一种方式可以把表达式树转换成可执行代码,然后输出执行结果呢?”,下面就用代码来解释这个问题。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
//引入Expressions<TDelegate>类的命名空间
using System.Linq.Expressions;

namespace ConsoleApp
{
    class Program
    {
        static void Main(string[] args)
        {

            //将Lambda表达式构造成表达式树
            Expression<Func<int, int, int>> expressionTree = (a, b) => a + b;

            //通过调用Compile()方法来 lambda 表达式的委托。
            Func<int, int, int> del = expressionTree.Compile();

            //调用委托实例,获得结果
            int result = del(5,10); //result为15

            Console.WriteLine("5 + 15 的和为:{0}", result);
            Console.ReadKey();

        }


    }
}

以上代码通过Expressions<TDelegate>类的Compile()方法将表达式树编译成委托实例,然后通过委托调用的方式得到两个数的和。



暗锚,解决锚点偏移

文章评论

    嘿,来试试登录吧!