[C# 基础知识系列]专题十四:深入理解Lambda表达式

引言:

   对于刚刚接触Lambda表达式的朋友们,可能会对Lambda表达式感到非常疑惑,它到底是个什么什么样的技术呢?以及它有什么好处和先进的地方呢?下面的介绍将会解除你这些疑惑。

 

一、Lambda表达式的演变过程

Lambda表达式其实大家可以理解为它是一个匿名函数(对于匿名函数的介绍大家可以参考我这篇文章), Lambda表达式可以包含表达式和语句,并且可以用于创建委托,以及C#编译器也能将它转换成表达式树。

对于Lambda表达式中都会使用这个运算符——“=>”,它读成“goes to” ,该运算符的左边为输入参数,右边是表达式或者语句块,下面就看看Lambda表达式是如何来创建委托实例(代码同时也给出了Lambda表达式从匿名方法的演示过程,从而帮助大家更好的理解Lambda表达式是匿名函数的概念,只不过C#3 中提出的Lambda表达式比匿名函数的使用更加简洁和直观了,其实原理都是一样的, 编译器同样会把Lambda表达式编译成匿名函数,也就是一个名字的方法):

using System;

namespace Lambda表达式Demo
{
    class Program
    {
        /// <summary>
        /// Lambda 表达式使用演示
        /// </summary>
        /// <param name="args"></param>
        static void Main(string[] args)
        {
            // Lambda表达式的演变过程
            // 下面是C# 1中创建委托实例的代码
            Func<string, int> delegatetest1 = new Func<string, int>(Callbackmethod);
            
            //// C# 2中用匿名方法来创建委托实例,此时就不需要额外定义回调方法Callbackmethod
            Func<string, int> delegatetest2 = delegate(string text)
            {
                return text.Length;
            };

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

            //// 可以省略参数类型string,把上面代码再简化为:
            Func<string, int> delegatetest4 = (text) => text.Length;

            //// 如果Lambda表达式只需一个参数,并且那个参数可以隐式指定类型时,
            // 此时可以把圆括号也省略,简化为:
            Func<string, int> delegatetest = text => text.Length;

            // 调用委托
            Console.WriteLine("使用Lambda表达式返回字符串的长度为: " + delegatetest("learning hard"));
            Console.Read();
        }

        /// <summary>
        /// 回调方法
        /// 如果使用了Lambda表达式和匿名函数,此方法就不需要额外定义
        /// </summary>
        /// <param name="text"></param>
        /// <returns></returns>
        private static int Callbackmethod(string text)
        {
            return text.Length;
        }
    }
}

运行结果为:

上面代码中都有详细的演变过程,这里就不多解释了,希望通过这部分之后,大家可以对Lambda表达式有进一步的理解,其实Lambda表达式就是匿名方法,其中使用Lambda表达式来创建委托实例,我们却没有指出创建的委托类型,其中编译器会帮助我们去推断委托类型,从而简化我们创建委托类型所需要的代码,从而更加简洁,所以Lambda表达式可以总结为——它是在匿名方法的基础上,再进一步地简化了创建委托实例所需要的代码。

二、Lambda表达式的使用

为了帮助大家更好的理解Lambda表达式,下面演示下用Lambda表达式来记录事件(代码中Lambda运算符的右边调用了一个回调方法ReportEvent()):

using System;
using System.Windows.Forms;

namespace Lambda表达式来记录事件Demo
{
    class Program
    {
        static void Main(string[] args)
        {
            // 新建一个button实例
            Button button1 = new Button() { Text ="点击我"};

            // C# 2中使用匿名方法来订阅事件
            //button1.Click+=delegate (object sender,EventArgs e)
            //{
            //    ReportEvent("Click事件", sender, e);
            //};
            //button1.KeyPress += delegate (object sender, KeyPressEventArgs e)
            //{
            //    ReportEvent("KeyPress事件,即键盘按下事件", sender, e);
            //};

            // C# 3Lambda表达式方式来订阅事件
            // 与上面使用匿名方法来订阅事件是不是看出简单了很多,并且也直观了
            button1.Click += (sender, e) => ReportEvent("Click事件", sender, e);
            button1.KeyPress += (sender, e) => ReportEvent("KeyPress事件,即键盘按下事件", sender, e);

            // C# 3之前初始化对象时使用下面代码
            //Form form = new Form();
            //form.Name = "在控制台中创建的窗体";
            //form.AutoSize = true;
            //form.Controls.Add(button1);

            // C# 3中使用对象初始化器
            // 与上面代码的比较中,也可以看出使用对象初始化之后代码简化了很多
            Form form = new Form { Name = "在控制台中创建的窗体", AutoSize = true, Controls = { button1 } };
          
            // 运行窗体
            Application.Run(form);
        }

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

运行结果:

从上面代码中可以看出,使用Lambda表达式之后代码确实简洁了很多,上面代码中都有详细的注释,这里就不解释了,大家可以查看代码中的注释来进行理解,并且代码中注释部分也列出了C# 3之前是如何实现这样的代码的, 这样有利于比较,从而帮助大家更好的认识到Lambda所带来的好处和进一步来理解Lambda表达式。

三、表达式树

上面指出Lambda表达式除了可以用来创建委托外,C#编译器还可以将他们转换成表达式树——用于表示Lambda表达式逻辑的一种数据结构,表达式树也可以称作表达式目录树,它将代码表示成一个对象树,而不是可执行的代码。对于刚接触哦表达式树的朋友肯定会问——为什么需要把Lambda表达式转化为表达式目录树呢?对于表达式树的提出主要是为后面Linq to SQL 做铺垫,一个Linq to SQL 的查询语句并不是在C#的程序中执行的,而是C#编译器把它转化为SQL 语句,然后再在数据库中执行。在我们使用Linq to SQL的时候都需要添加一个Linq to SQL的类,该类的扩展名dbml,该的作用就是帮助我们把Linq to SQL 的语句映射为SQL语句,然后再在数据库中执行SQL语句,把返回的结果再返回给一个IQueryable集合,所以Linq to SQL 也采用了通常的ORM(Object—Relationship—Mapping)来设计的,相当于是一个ORM框架,不过这个框架只能与微软的SQL server数据库进行映射,对于其他类型的数据库却不可以,然而很多其他开发人员却对此进行了一些扩展,扩展了对其他数据库的支持。前不久还在博客园中发布了开源的Linq框架的,名字为ELinq,其他它就是对Linq to SQL的一个扩展,使Linq语句可以映射到其他数据库的查询语句。

下面先看看如何把Lambda表达式转化为表达式目录树(其中需要引入一个新的命名空间—— System.Linq.Expressions):

using System;

// 引用额外的命名空间
using System.Linq.Expressions;

namespace 表达式树Demo
{
    class Program
    {
        /// <summary>
        /// 表达式树的演示
        /// </summary>
        /// <param name="args"></param>
        static void Main(string[] args)
        {
            #region 将Lambda表达式转换为表达式树演示
            // 将Lambda表达式转换为Express<T>的表达式树
            // 此时express不是可执行的代码,它现在是一个表达式树的数据结构
            Console.WriteLine("将Lambda表达式转化为表达式树的演示:");
            Expression<Func<int, int, int>> expression = (a, b) => a + b;

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

            // 既然叫做树,那肯定有左右节点
            // 获取表达式树的主体部分
            BinaryExpression body = (BinaryExpression)expression.Body;
            
            // 左节点,每个节点本身就是一个表达式对象
            ParameterExpression left = (ParameterExpression)body.Left;

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

            Console.WriteLine("表达式主体为:");
            Console.WriteLine(expression.Body);
            Console.WriteLine("表达式树左节点为:{0}{4} 节点类型为:{1}{4}{4} 表达式右节点为:{2}{4} 节点类型为:{3}{4}", left.Name, left.NodeType, right.Name, right.NodeType,Environment.NewLine);
            Console.Read();
            #endregion 

            #region 把表达式树转化回可执行代码

            // Compile方法生成Lambda表达式的委托
            Console.WriteLine("按下Enter键进入将表达式树转换为Lambda表达式的委托演示:");
            int result = expression.Compile()(2, 3);
            Console.WriteLine("调用Lambda表达式委托结果为:" + result);
            Console.ReadKey();
            #endregion
        }
    }
}

运行结果:

上面代码首先把Lambda表达式转化为表达式树,下面这行代码就是把Lambda表达式转化为表达式树:

 Expression<Func<int, int, int>> expression = (a, b) => a + b;

之后对于表达式树这种数据结构进行分析来获得该树中的主体和左右节点是什么,获得主体和左右节点的代码如下:

 // 获取表达式树的主体部分
            BinaryExpression body = (BinaryExpression)expression.Body;
            
            // 左节点,每个节点本身就是一个表达式对象
            ParameterExpression left = (ParameterExpression)body.Left;

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

从上面代码可以得出——树中的每个节点都是一个表达式(ParameterExpression和BinaryExpression都是继承Expression的,所以左右节点都是表达式),分析完表达式树之后,代码中还演示了如果把表达式树转化为可执行的代码,即转化为Lambda表达式的委托对象(此时调用Expression的Compile()方法来转化为可执行代码),通过调用委托来获得结果。 

 关于Lambda表达式树的更多信息还可以参看这篇博客:http://www.cnblogs.com/tianfan/archive/2010/03/05/expression-tree-basics.html (博主翻译的还可以) 

四、总结

 到这里本专题的内容也介绍的差不多了,希望通过本专题使一些之前对Lambda表达式感到疑惑的朋友们现在可以理解Lambda表达式,因为只有理解好Lambda表达式之后,对于Linq的学习就可以说是轻而易举了。

 补充:

1.匿名函数不等于匿名方法,匿名函数包含了匿名方法和lambda表达式这两种概念。
匿名函数:{匿名方法,lambda表达式}

lambda作为表达式,可以被C#编译器转换为委托,也可以被编译器转换为表达式树,匿名方法只能转换为委托。

两者的共通点是都能被编译器转换成为委托,lambda表达式能完成几乎所有匿名方法能完成的事。

作为委托和表达式树,两者在IL阶段表示就不一样了。作为委托的IL,在运行期间直接被CLR所执行,而作为表达式树,是不被CLR所直接执行,而是通过相应的Provider转换为所需要的东西,比如说可以转换为SQL,也可以转换为JAVA。(引自留言中浪雪朋友的意见)

本专题中演示源码:https://files.cnblogs.com/zhili/Lambda%E8%A1%A8%E8%BE%BE%E5%BC%8FDemo.zip

 

 

posted @ 2012-12-12 10:19  Learning hard  阅读(11464)  评论(25编辑  收藏  举报