C#高级编程之Lambda&Linq

Lambda的前世今生

  在C#2.0之前,当我们执行委托绑定的方法时,具体如下所示:

    public class LambdaDemo
    {
        //public delegate void NoReturnNoParam();
        public delegate void NoReturnWithParam(int x, int y);
        //public delegate int WithReturnNoParam();
        //public delegate string WithReturnWithParam(int x, ref int y);
    
        //Lambda的前世今生
        public   void  Show()
        {
            //.net framework 1.0/1.1时代
            {
                //NoReturnNoParam method = new NoReturnNoParam(DoNothing);
                //method.Invoke();
                NoReturnWithParam method1 = new NoReturnWithParam(MethodWithParam);
                method1.Invoke(1, 2);
            }
       private void MethodWithParam(int x, int y)
        {
            Console.WriteLine("this is a method with param x,y,without return value");
        }
            

在C# 2.0时代,提出了匿名方法,增加了 一个delegate关键字,可以访问到除了参数以外的局部变量eg此处的float i=100

    double i = 100.1;
            {

                //2.0时代,匿名方法,增加了 一个delegate关键字,可以访问到除了参数以外的局部变量eg此处的float i=100
                NoReturnWithParam method = new NoReturnWithParam(delegate (int x, int y)
                 {
                     Console.WriteLine($"param1 is{x},param2 is {y}");
                     Console.WriteLine(i);
                 }
                );

            }

在C#3.0时代,去掉了delegate关键字,并在紧跟参数列表后面添加了一个=>  goes to 

//.net 3.0时代,去掉了delegate关键字,并在紧跟参数列表后面添加了一个=>  goes to 
            {
                NoReturnWithParam method = new NoReturnWithParam((int x, int y) =>
                {
                    Console.WriteLine($"param1 is{x},param2 is {y}");
                    Console.WriteLine(i);
                }
                );
            }

到C# 3.0后期,去掉了匿名方法的参数列表的类型。

    {
                NoReturnWithParam method = new NoReturnWithParam((x, y) =>   //编译器自动推断来的(语法糖)
                {
                    Console.WriteLine($"param1 is{x},param2 is {y}");
                    Console.WriteLine(i);
                }
                );
            }

该匿名方法在IL中如下:

 

 

对于以下情况,还可以更加精简:

    {
                //如果匿名方法体中只有一行代码,可以直接省去方法体的大括号
                NoReturnWithParam method = new NoReturnWithParam((x, y) => Console.WriteLine($"param1 is{x},param2 is {y}"));
                method.Invoke(1, 3);
            }
            {
                //一个参数的时候,无返回值的
                Action<string> method = s => Console.WriteLine("with one param of lambda");
                method.Invoke("hello");

            }
            //有返回值的情况?如果lambda表达式只有一行代码,且有返回值,则可以省略return
            {
                Func<string> func = () => "weiyin";
                Func<int, string> func1 = t => t.ToString();
            }

Linq的逐步演化

Linq的演化是基于扩展方法来的。

扩展方法的使用

对于扩展方法,此处举个小例子,比如对于实现int与其他类型的相加、字符串截取功能。如下所示:

class Program
    {
        static void Main(string[] args)
        {
            LambdaDemo lambdaDemo = new LambdaDemo();
            lambdaDemo.Show();
            {
                Student student = new Student()
                {
                    Id = 1,
                    Name = "weiyin",
                    Age = 28
                };
                student.StudyMath();
                {
                    int i = 1;
                    int? j = 20;
                    //int k = i + j;会报错
                    int k = i + j.ToInt();

                }
                {
                    string str = "拓展方法实现字符串截取截取截取截取截取截取截取";
                    Console.WriteLine(str.ToStringLength());
                }
            }
/// <summary>
    /// 拓展方法的要求
    /// </summary>
    public static class MethdoExtension
    {
        public static void StudyMath(this Student stu)
        {
            Console.WriteLine("student增加的一个学习数学的拓展方法");
        }
        public static int ToInt(this int ?i)
        {
            return i ?? 0;//与下面的语句功能相同。
            //if (i == null)
            //{
            //    return 0;
            //}
            //else
            //return i.Value;
        }
        /// <summary>
        /// 其实也可以对Object类型进行拓展,拓展Object以后,任何一个类型都可以来调用,因为Object是所有类型的父类---拓展方法可以被继承。
        /// 1拓展Oeject 类型以后,会造成类型的方法污染
        /// 2 除非需求很明确,否则不要随意的拓展Object或者没有约束的泛型
        /// </summary>
        /// <param name="str"></param>
        /// <param name="length"></param>
        /// <returns></returns>
        public static string ToStringLength(this string str,int length=10)
        {
            if(string.IsNullOrEmpty(str))
            {
                return string.Empty;
            }
            else if(str.Length>length)
            {
                return $"{str.Substring(0,length)}......";
            }
            else
            {
                return str;
            }
        }
    }

LINQ演变

此处定义了一个返回List<Student>集合的一个方法,如下:

public static List<Student> GetStudentsList()
        {
            List<Student> studentsList = new List<Student>();
            Student student1 = new Student()
            {
                Id = 1,
                Name = "weiyin1",
                Age = 28
            };
            Student student2 = new Student()
            {
                Id = 1,
                Name = "weiyin2",
                Age = 29
            };
            Student student3 = new Student()
            {
                Id = 1,
                Name = "weiyin3",
                Age = 30
            };
            Student student4 = new Student()
            {
                Id = 1,
                Name = "weiyin4",
                Age = 31
            };
            Student student5 = new Student()
            {
                Id = 1,
                Name = "weiyin5",
                Age = 32
            };
            Student student6 = new Student()
            {
                Id = 1,
                Name = "weiyin6",
                Age = 33
            };
            studentsList.Add(student1);
            studentsList.Add(student2);
            studentsList.Add(student3);
            studentsList.Add(student4);
            studentsList.Add(student5);
            studentsList.Add(student6);
            return studentsList;
        }
View Code

如果想获取年龄大于30的集合,如何获取呢?最简单的如下:

    /// <summary>
        /// 输入的是 List<Student>,返回的也是 List<Student>
        /// </summary>
        /// <param name="stulist"></param>
        /// <returns></returns>
        public static List<Student> GetStudentListAgeBiggerthan30(List<Student> stulist)
        {
            List<Student> res = new List<Student>();
            foreach (var item in stulist)
            {
                if (item.Age > 30)//条件的判断其实就是逻辑---就是动作、唯一的区别就是中间这里的条件不一样
                {
                    res.Add(item);
                }
            }
            return res;
        }

如果进一步采用委托将里面的逻辑(动作)抽出来呢【其返回的是一个bool类型,操作的是item Student对象】。如下:

//如果采用拓展函数加委托呢。
        /// <summary>
        /// 使用委托:委托可以把方法当做参数传递;方法其实是逻辑,委托可以把逻辑当做参数传递
        /// 委托:应该是返回值为bool的委托,参数是一个Student
        /// </summary>
        /// <returns></returns>
        public static List<Student> MySelect(this List<Student> resource, Func<Student, bool> func)
        {
            List<Student> list = new List<Student>();
            foreach (var item in resource)
            {
                if (func.Invoke(item)) //if (item.Age > 30)//条件的判断其实就是逻辑---就是动作、、唯一的区别就是中间这里的条件不一样
                {
                    list.Add(item);
                }
            }
            return list;
        }

如果不指定具体的类型Student,Func改为Predicate特定的返回值为bool的委托,则泛型版如下:

/// <summary>
        /// 泛型拓展方法,满足不同类型需要
        /// </summary>
        /// <typeparam name="TSource"></typeparam>
        /// <param name="resource"></param>
        /// <param name="func"></param>
        /// <returns></returns>
        public static List<TSource> MySelect<TSource>(this List<TSource> resource, Predicate<TSource> tPredicate)
        {
            List<TSource> list = new List<TSource>();
            foreach (var item in resource)
            {
                if (tPredicate.Invoke(item)) //if (item.Age > 30)//条件的判断其实就是逻辑---就是动作、、唯一的区别就是中间这里的条件不一样
                {
                    list.Add(item);
                }
            }
            return list;
        }

如果不想对委托的返回类型约定那么死,想根据用户预想的类型返回。那我们看下Enumerable类下的Select方法签名:

    //
        // 摘要:
        //     将序列中的每个元素投影到新表单。
        //
        // 参数:
        //   source:
        //     一个值序列,要对该序列调用转换函数。
        //
        //   selector:
        //     应用于每个元素的转换函数。
        //
        // 类型参数:
        //   TSource:
        //     中的元素的类型 source。
        //
        //   TResult:
        //     返回的值的类型 selector。
        //
        // 返回结果:
        //     System.Collections.Generic.IEnumerable`1 其元素是调用转换函数的每个元素的结果 source。
        //
        // 异常:
        //   T:System.ArgumentNullException:
        //     source 或 selector 为 null。
        public static IEnumerable<TResult> Select<TSource, TResult>(this IEnumerable<TSource> source, Func<TSource, TResult> selector);
View Code

两者是不是很相像。

分别用系统内置的Select方法和自定义的MySelect查询数据结果如下:

List<Student> studentList = GetStudentsList();
            var res = LinqDemo.GetStudentListAgeBiggerthan30(studentList);
            //Func<Student, bool> func = item => item.Age > 30;
            //studentList.Mywhere(func);
            var res1 = studentList.MySelect(item => item.Age > 30);
            var result = studentList.Select(t => t.Age > 30);

 个人总结:

Lambda表达式是本质是基于匿名方法(在IL中就是一个符合指定签名的方法),经过步步简化后的语法糖,它作为委托实例的参数,绑定了委托实例将要执行的签名方法。

LINQ是基于扩展方法和Lambda表达式,通过执行特定泛型类型的扩展方法(改拓展方法中写明了不变与可变【委托绑定的方法】的部分),通过传入Lambda表达式【委托绑定的方法,委托实例的参数】,执行特定逻辑动作。

posted @ 2020-12-10 17:32  liweiyin  阅读(249)  评论(0编辑  收藏  举报