• Linq快速入门——Lambda表达式的前世今生


    Lambda表达式其实并不陌生,他的前生就是匿名函数,所以要谈Lambda表达式,就不得不谈匿名函数,要谈匿名函数,那又要不得不谈委托。

    何为委托

    委托非常好理解,类似于C++里面的函数指针(指向了一个方法),并且委托约束了待指向方法的签名(由返回类型和参数组成)。

    复制代码
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    
    namespace 委托Test
    {
        delegate bool FilterDelegate(int i);
        class Program
        {
            static void Main(string[] args)
            {
                int[] array = { 1, 2, 3, 5, 6, 6, 7, 8, 9 };
                List<int> newList = MyFilter(array,FilterOdd);
                foreach (int item in newList)
                {
                    Console.WriteLine(item);
                }
                Console.ReadKey();
    
            }
            static List<int> MyFilter(int[] array, FilterDelegate filter)
            {
                List<int> list = new List<int>();
                for (int i = 0; i < array.Length; i++)
                {
                    if (filter(i))
                    {
                        list.Add(i);
                    }
                }
                return list;
            }
            /// <summary>
            /// 偶数
            /// </summary>
            /// <param name="i"></param>
            /// <returns></returns>
            static bool FilterEven(int i)
            {
                return i % 2 == 0;
            }
            /// <summary>
            /// 奇数
            /// </summary>
            /// <param name="i"></param>
            /// <returns></returns>
            static bool FilterOdd(int i)
            {
                return i % 2 == 1;
            }
        }
    }
    复制代码

    对于上面这个Demo可以看出,我需要定义了两个方法(FilterOdd,FilterEven),让我的委托变量指向这两个方法。但有时候申明方法很麻烦,还要考虑方法名称不重复,所以对于一些我们只使用一次的方法,完全没有必要单独为其申明,使用匿名方法即可(C# 2.0为程序员提供了匿名方法),大大简化了操作

    匿名方法

    //例如
    delegate void Del(int x);
    ....
    Del d = delegate(int k) { /* ... */ };

    所以上面例子小小改动一下即可:

    复制代码
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    
    namespace 委托Test
    {
        delegate bool FilterDelegate(int i);
        class Program
        {
            static void Main(string[] args)
            {
                int[] array = { 1, 2, 3, 5, 6, 6, 7, 8, 9 };
                //使用匿名方法来求偶数
                List<int> newList = MyFilter(array, delegate(int i) {
    
                    return i % 2 == 0;
                });
             
                foreach (int item in newList)
                {
                    Console.WriteLine(item);
                }
                Console.ReadKey();
    
            }
            static List<int> MyFilter(int[] array, FilterDelegate filter)
            {
                List<int> list = new List<int>();
                for (int i = 0; i < array.Length; i++)
                {
                    if (filter(i))
                    {
                        list.Add(i);
                    }
                }
                return list;
            }
        }
    }
    复制代码

    Lambda表达式特性

    • C# 2.0中加入的匿名方法,简化了我们编写事件处理函数的工作,使我们不再需要单独声明一个函数来与事件绑定,只需要使用delegate关键字在线编写事件处理代码。
    • 而C# 3.0则更进一步,通过Lambda表达式,我们可以一种更为简洁方式编写事件处理代码,新的Lambda事件处理代码看上去就像一个计算表达式,它使用"=>"符号来连接事件参数和事件处理代码。我可以这样写:SomeEvent += 事件参数 => 事件处理代码;

    所以上面代码稍稍修改后,用Lambda表达式来替换匿名方法:

    复制代码
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    
    namespace 委托Test
    {
        delegate bool FilterDelegate(int i);
        class Program
        {
            static void Main(string[] args)
            {
                int[] array = { 1, 2, 3, 5, 6, 6, 7, 8, 9 };
                //使用Lambda表达式来求偶数
                List<int> newList = MyFilter(array, i => i % 2==0);
             
                foreach (int item in newList)
                {
                    Console.WriteLine(item);
                }
                Console.ReadKey();
    
            }
            static List<int> MyFilter(int[] array, FilterDelegate filter)
            {
                List<int> list = new List<int>();
                for (int i = 0; i < array.Length; i++)
                {
                    if (filter(i))
                    {
                        list.Add(i);
                    }
                }
                return list;
            }
        }
    }
    复制代码

    注意:

    • 使用Lambda表达式,"=>"之前为参数列表,如果有多个参数,则不能省略括号,比如:(s,e)=>....
    • 如果方法有返回值,并且处理代码只有一行,可以简写成i=>i%2==0,等价于i=>{return i%2==0},反之对于有多行的处理代码,则不能简写,必须写完整,比如:(s,e)=>{...程序代码块...}

    我们再来看看System.Linq名称空间下的扩展方法有什么特征:

    第一个参数为扩展方法,我已经在前一篇文章《Linq快速入门——扩展方法》里提到了,我不做具体解释了,简单来说创建扩展方法就是这四步:

    • 创建一个名为MyHelper的类,约定了此类中的方法均是扩展方法。注意这个类必须是静态类(Static)
    • 扩展方法必须是Static静态方法
    • 第一个参数为待扩展的类型,前面标注this
    • 如果MyHelper在一个类库中,记得对其添加引用using相关名称空间

    对于第二个参数:System.Func<TSource, bool> predicate),我们再来深究下。

    Fun<T,TResult>  and  Action<T>

    • Fun<T,TResult>:此委托封装一个具有一个参数并返回 TResult 参数指定的类型值的方法。所以在使用 Func<T, TResult> 委托时,不必显式定义一个封装只有一个参数的方法并且其返回类型TResut委托
    • Action<T>:此委托封装一个方法,该方法只有一个参数并且不返回值。所以在使用 Action<T> 委托时,不必显式定义一个封装只有一个参数的方法(并且不能返回值)的委托。 

     所以再对上面的Filter进行改进:

    复制代码
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    
    namespace 委托Test
    {
        //delegate bool FilterDelegate(int i);
        class Program
        {
            static void Main(string[] args)
            {
                int[] array = { 1, 2, 3, 5, 6, 6, 7, 8, 9 };
                //使用匿名方法来求偶数
                //List<int> newList = MyFilter(array, delegate(int i) {
    
                //    return i % 2 == 0;
                //});
                //使用Lambda表达式求偶数
                List<int> newList = MyFilter(array, i => i % 2 == 0);
             
                foreach (int item in newList)
                {
                    Console.WriteLine(item);
                }
                Console.ReadKey();
    
            }
            //Func<int,bool>: 封装了一个具有一个int参数并且返回类型为bool类型的方法
            static List<int> MyFilter(int[] array,Func<int,bool> filter)
            {
                List<int> list = new List<int>();
                for (int i = 0; i < array.Length; i++)
                {
                    if (filter(i))
                    {
                        list.Add(i);
                    }
                }
                return list;
            }
        }
    }
    复制代码

    回顾,A Simple Lambda Demo

    •  下面Demo首先申明 Func<T, TResult> 变量,并为其分配了一个 lambda 表达式。
    •  随后将封装此方法的委托(看下面实例)传递给Enumerable.Where、Enumerable.Order、 Enumerable.Select 方法,以将字符串数组中的字符串进行处理。
    • ForEach 和 ForEach<T> 方法都采用 Action<T> 委托作为参数。 通过使用由委托封装的方法,可以对数组或列表中的每个元素执行操作
    复制代码
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    
    namespace LambdaDemo
    {
        class Program
        {
            static void Main(string[] args)
            {
               
                string[] names = {"Eyes","Voodoo","Tod","Chris","Christina","Maxisim" };
                Func<string, bool> filter = s => s.Length > 5;
                Func<string, string> order = s => s;
                Func<string, string> operating = s => s.ToUpper();
    
                IEnumerable<string> expr = names.Where(filter).OrderByDescending(order).Select(operating);
                expr.ToList<string>().ForEach(i => Console.WriteLine(i));
                Console.ReadKey();
    
            }
        }
    }
    复制代码

    Lambda表达式树

    • 表达式树表示树状数据结构的代码,树状结构中的每个节点都是一个表达式,例如一个方法调用或类似 x < y 的二元运算。
    • 并且你可以编译和运行由表达式树所表示的代码。这样的优势就是表达式树可以在运行的时候编译运行,而且可以对lambda表达式进行动态修改。
    • 若要使用 API 创建表达式树,请使用 Expression 类。 此类包含创建特定类型的表达式树节点的静态工厂方法,例如,ParameterExpression(表示一个变量或参数),ConstantExpression(表示一个常量),MethodCallExpression(表示一个方法调用)。 ParameterExpression 、MethodCallExpression、ConstantExpression 以及其他表达式特定的类型也在 System.Linq.Expressions 命名空间中定义。 这些类型派生自抽象类型 Expression。

    例如将表达式(Price-5)*Count*Rebate表示成一棵二叉树可以用以下方式表达:

     

    复制代码
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Linq.Expressions;
    
    namespace Lambda表达式树
    {
        class Program
        {
            static void Main(string[] args)
            {
                //计算(Price-5)*Count*Rebate
                ParameterExpression paraPrice = Expression.Parameter(typeof(decimal),"price");
                ConstantExpression constant = Expression.Constant(5m,typeof(decimal));
                BinaryExpression result1 = Expression.Subtract(paraPrice, constant);
    
                ParameterExpression paraCount = Expression.Parameter(typeof(decimal),"count");
                ParameterExpression paraRebate = Expression.Parameter(typeof(decimal),"rebate");
                BinaryExpression result2 = Expression.Multiply(paraCount,paraRebate);
    
                BinaryExpression result3 = Expression.Multiply(result1,result2);
                Expression<Func<decimal, decimal, decimal, decimal>> totalPrice = Expression.Lambda<Func<decimal, decimal, decimal, decimal>>(result3,paraPrice,paraCount,paraRebate);
                Func<decimal, decimal, decimal, decimal> myFun = totalPrice.Compile();
                Console.WriteLine(myFun(125m,10m,0.5m));
                Console.ReadKey();
    
            }
          
        }
    }
    复制代码

    分析表达式树

    Expression<TDelegate> 类型提供 Compile 方法,该方法将表达式树表示的代码编译成一个可执行委托。

    复制代码
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Linq.Expressions;
    
    
    namespace ConsoleApplication2
    {
        class Program
        {
            static void Main(string[] args)
            {
             
                Expression<Func<int, int>> f1 = x => x + 1;
                //f1(1)//...错误,必须将表达式树表示的代码编译成一个可执行委托
                Func<int, int> f2 = f1.Compile();
                Console.WriteLine(f2(2));
                Console.ReadKey();
            }
        }
    }
    复制代码

    总结

    未完,持续更新中

     

    Lambda表达式的演化,委托-匿名方法-Func-Lambda

    匿名方法

    很多时候委托接收的方法是一次性的或者方法体是非常简单的...

    例三:

    我们可以写成:

    有没有发现我们每次都要定义委托,很多时候签名可能是一样的。这样就没有必要定义重复的。

    然后又过了很久很久...

    Func和Action

    可能老大也觉得我们每次定义委托有点傻,所以干脆在框架内一次定义好所有可能用到的委托。那千变万化的方法签名怎么可能定义得全?没关系,定义成泛型的不就可以了吗。

    先说Func:

    细心的朋友可能看到了,Func相对于AddDelegate多定义了一个int。多出了的那个是指的是返回类型。我们F12看对应的定义:

    关于上面Func的写法我们可以简写成:(语法糖而已,编译后还是注释的代码

    再看Action:

    提醒:以后如果我们写代码的时候如果写到到delegate...,你要马上想到是否可以用Func或者Action来代替呢?C#4中的Action和Func有16个参数,足够你用了。

    我们等了又等,又过了好久好久...

    Lambda的诞生

    我XX,这TM就是亲兄弟啊。直接去掉delegate关键字,然后加一个=>就成了lambda表达式了。(=>读作goes to

    我们继续简化:

    丢掉参数类型也是可以的,因为强大的VS可以根据泛型委托Func自己推断出来参数类型。

    还可以简化吗?当然:

    return关键字也不要了,大括号也不要了。(当然,方法体只有单条语句才能怎么做

    现在看起来已经非常接*我们*时用的Lambda表达式了。

    如果传入参数只有一个的话,我们还可以继续简化:

    这就是我们*时见得最多的lambda长相了。

    要长成这样也是有要求的:

    1. 只能有一个传入参数
    2. 方法体只能只一条语句。

    关于第1点,lambda我们*时用得较多的是基于IEnumerable或IQueryable,所以只能有一个参数也就满足了。

    关于第2点,我们使用扩展方法的链式编程来解决。

    如:(用链式来解决第二点)

    从此,我们过上了幸福的生活...

    借《深入理解C#》中的一图:

  • 相关阅读:
    线性回归和逻辑回归
    行列式,叉积 (2)
    K最邻近算法(K-Nearest Neighbor,KNN)(初探)
    线性感知机和SVM(初探)
    向量点积(Dot Product)
    聚类(初探)
    sqlserver全文检索
    诗词背诵
    初级英语04
    初级英语03
  • 原文地址:https://www.cnblogs.com/cuihongyu3503319/p/2829705.html
Copyright © 2020-2023  润新知