WPF-MVVM形式学习笔记4——Lambda表达式学习

WPF-MVVM模式学习笔记4——Lambda表达式学习

    在学习MVVM的过程中,其中自定义了一个超类NotificationObject,如下

    public abstract class NotificationObject : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;

        protected virtual void RaisePropertyChanged(string propertyName)
        {
            PropertyChangedEventHandler handler = this.PropertyChanged;
            if (handler != null)
            {
                handler(this, new PropertyChangedEventArgs(propertyName));
            }
        }

        protected void RaisePropertyChanged(params string[] propertyNames)
        {
            if (propertyNames == null) throw new ArgumentNullException("propertyNames");

            foreach (var name in propertyNames)
            {
                this.RaisePropertyChanged(name);
            }
        }

        protected void RaisePropertyChanged<T>(Expression<Func<T>> propertyExpression)
        {
            var propertyName = ExtractPropertyName(propertyExpression);
            this.RaisePropertyChanged(propertyName);
        }

        public static string ExtractPropertyName<T>(Expression<Func<T>> propertyExpression)
        {
            if (propertyExpression == null)
            {
                throw new ArgumentNullException("propertyExpression");
            }

            var memberExpression = propertyExpression.Body as MemberExpression;
            if (memberExpression == null)
            {
                throw new ArgumentException("PropertySupport_NotMemberAccessExpression_Exception", "propertyExpression");
            }

            var property = memberExpression.Member as PropertyInfo;
            if (property == null)
            {
                throw new ArgumentException("PropertySupport_ExpressionNotProperty_Exception", "propertyExpression");
            }

            var getMethod = property.GetGetMethod(true);
            if (getMethod.IsStatic)
            {
                throw new ArgumentException("PropertySupport_StaticExpression_Exception", "propertyExpression");
            }

            return memberExpression.Member.Name;
        }

    }


      其中有一个函数 protected void RaisePropertyChanged<T>(Expression<Func<T>> propertyExpression),这里有个Expression<Func<T>我不是很懂,后来查查是lambda表达式,好吧,说实话,我看到这个Lambda有点蛋疼菊紧...之前碰到过,没有深究,这次再试着去学习理解一下,在网上看了很多资料,其中有一位小伙伴讲的不错的,摘抄下来,哈哈。(我建议直接先去看原文,然后再返回来看我的而文章,因为我下面的例子是基于WPF讲解的,并且像一些Button的命令绑定都贱贱的用上了,担心看的人再看蒙圈了,点此阅读原文

lambda简介

    lambda运算符简化了匿名委托的使用,使代码更加简洁、优雅,据说它是微软自C#1.0后新增的最重要的功能之一。

    lambda运算符:所有的lambda表达式都是用新的lambda运算符“ => ”,可以称呼这个运算符为“转到”或者“成为”。运算符将表达式分为两部分,左边指定输入参数,右边是lambda的主体。

    lambda表达式:

           1. 一个参数: param => expression

           2. 多个参数: (paramlist) => expression

    上面这些东西,先记着,看完下面的再反过来看,理解更深刻。

lambda有啥好?

    根据一个demo,慢慢进行分析,例子如下

namespace LambdaDemo.ViewModel
{
    public class Person
    {
        public string Name { get; set; }
        public int Age { get; set; }
    }

    public class VMLambdaStudyDemo1
    {
        public static List<Person> PersonsList()
        {
            List<Person> persons = new List<Person>();

            for (int i = 0; i < 7; i++)
            {
                Person p = new Person() {Name = i + "儿子",Age = 8-i};
                persons.Add(p);
            }
            return persons;
        }
        /// <summary>
        /// Btn_LambdaDemo1 Click事件
        /// </summary>
        public void Btn_LambdaDemo1_Click()
        {
            List<Person> persons = PersonsList();

            persons = persons.Where(p => p.Age > 5).ToList(); //所有Age>5的Person的集合

        }
    }

}

   上面的Demo工程为按下按钮Btn_LambdaDemo1后,只保留persons集合中的Age大于5的元素。这样直接看,对于我这个新手不明显,我还是喜欢打断点追踪,如下图

WPF-MVVM形式学习笔记4——Lambda表达式学习

   我在33行和35行、37行各打一个断点,运行程序

WPF-MVVM形式学习笔记4——Lambda表达式学习

  点击按钮Btn_LambdaDemo1,程序停留在第一个断点处

  WPF-MVVM形式学习笔记4——Lambda表达式学习

  可以看到此时persons集合中元素为空,紧接着 F5 运行到下一个断点

WPF-MVVM形式学习笔记4——Lambda表达式学习

此时我们发现,集合persons中已经添加了7个元素,接着F5运行到下一个断点

WPF-MVVM形式学习笔记4——Lambda表达式学习

     这个时候就会发现,集合persons中只剩下3个元素,这3个元素均满足我们的筛选条件。

     到这里,可以看出咱们的这个lambda表达式 p => p.Age > 5  起作用了,但是这还不能看出lambda的好处,大家也知道,好是相对的嘛,那咱就不用lambda实现这个筛选功能,换另一种方式

WPF-MVVM形式学习笔记4——Lambda表达式学习

    如上图,我将35行屏蔽,用37行-46行代码实现这个筛选功能,你可以打断点追踪一下,最后也能实现我们的要求。到这里,上面两种方式两两比较,咱们就能像张同学说的那样“lambda确实是一个甜枣”。(这个工程比较简单,如有需要,可自行下载,点此下载

lambda确实挺好

    上面的例子中,我用一种普通的方法来与lambda比较,来说明lambda的好处,但其实理论上不应该这样比较的。因为我在前面lambda简介中提到lambda简化了匿名委托的应用,使代码更加简洁,所以理论上我应该用匿名委托的方式来衬托一下lambda的好。下面就看一下 (p=>p.Age>5)这样的表达式到底是怎么回事,我将上面的代码修改为下

namespace LambdaDemo.ViewModel
{
    public class Person
    {
        public string Name { get; set; }
        public int Age { get; set; }
    }

    public class VMLambdaStudyDemo1
    {
        //委托
        delegate List<Person> UNamedWeiTuo(List<Person> persons);

        public static List<Person> PersonsList()
        {
            List<Person> persons = new List<Person>();

            for (int i = 0; i < 7; i++)
            {
                Person p = new Person() {Name = i + "儿子",Age = 8-i};
                persons.Add(p);
            }
            return persons;
        }

        public void Btn_LambdaDemo1_Click()
        {
            List<Person> persons = PersonsList();

            UNamedWeiTuo uwt = WeiTuoFunc;

            persons = uwt(persons);
   
        }

        //委托方法
        public static List<Person> WeiTuoFunc(List<Person> persons)
        {
            List<Person> personstmp = new List<Person>();
            foreach (Person p in persons)
            {
                if (p.Age > 5)
                {
                    personstmp.Add(p);
                }
            }

            return personstmp;
        }
    }

}

   在上面的代码中,我定义了一个委托UNamedWeiTuo(关于委托我就不多说了哈,请自行脑补),利用委托方法实现我们对集合persons的删选的要求。将上述的委托与lambda表达式 (p=>p.Age>5)一比较,我们就很可以看出来表达式的优点。同时也可以看出:其实表达式 (p=>p.Age>6) 中的 p 就代表委托方法中的参数,而表达式符号右边的 

p.Age>5 就代表委托方法的返回结果。

lambda趁热打铁

   下面就直接上张同学的更加直观的例子:

   1.单个参数

   使用委托方法

    

    public class VMLambdaStudyDemo1
    {
        //委托  逛超市
        delegate int GuangChaoShi(int a);
        public void Btn_LambdaDemo1_Click()
        {
            GuangChaoShi gwl = JieZhang;
            MessageBox.Show(gwl(10) + ""); //打印结果为 "20"
   
        }

        public static int JieZhang(int a)
        {
            return (a + 10);
        }
    }

   使用lambda表达式

    public class VMLambdaStudyDemo1
    {
        //委托  逛超市
        delegate int GuangChaoShi(int a);
        public void Btn_LambdaDemo1_Click()
        {
            GuangChaoShi gwl = p => p + 10;
            MessageBox.Show(gwl(10) + ""); //打印结果为 "20"
   
        }

    }
   

   Oh My LadyGaGa,That's greatful!,下面再来个多参数的,但是委托方法内部运算比较简单的。

   2.多参数,主体运算简单

   使用委托方法

    public class VMLambdaStudyDemo1
    {
        //委托  逛超市
        delegate int GuangChaoShi(int a,int b);
        public void Btn_LambdaDemo1_Click()
        {
            GuangChaoShi gwl = JieZhang;
            MessageBox.Show(gwl(10,100) + ""); //打印结果为 "80"
   
        }

        //结账
        public static int JieZhang(int a, int b )
        {
            return (b - (a + 10));
        }
    }


   使用lambda表达式

    public class VMLambdaStudyDemo1
    {
        //委托  逛超市
        delegate int GuangChaoShi(int a,int b);
        public void Btn_LambdaDemo1_Click()
        {
            GuangChaoShi gwl = (p,z) => z - (p + 10);
            MessageBox.Show(gwl(10,100) + ""); //打印结果为 "80"
   
        }

    }


   不错不错,下面还是来个多参数的,但是委托方法内部运算较为复杂的

  3.多参数,主题运算复杂

  使用委托方法

    public class VMLambdaStudyDemo1
    {
        
        /// <summary>
        ///委托  逛超市
        /// </summary>
        /// <param name="a">花费</param>
        /// <param name="b">付钱</param>
        /// <returns>找零</returns>
        delegate int GuangChaoShi(int a,int b);
        public void Btn_LambdaDemo1_Click()
        {
            GuangChaoShi gwl = JieZhang;
            MessageBox.Show(gwl(20,100) + ""); //打印结果为 "70"
   
        }

        //结账
        public static int JieZhang(int a, int b )
        {
            int zuidixiaofei = 10;
            if (b < zuidixiaofei)
            {
                return 100;
            }
            else
            {
                return (b - a - zuidixiaofei);
            }

        }
    }


  使用lambda表达式

    public class VMLambdaStudyDemo1
    {
        
        /// <summary>
        ///委托  逛超市
        /// </summary>
        /// <param name="a">花费</param>
        /// <param name="b">付钱</param>
        /// <returns>找零</returns>
        delegate int GuangChaoShi(int a,int b);
        public void Btn_LambdaDemo1_Click()
        {
            GuangChaoShi gwl = (p, z) =>
                {
                    int zuidixiaofei = 10;
                    if (p < zuidixiaofei)
                    {
                        return 100;
                    }
                    else
                    {
                        return (z - p - zuidixiaofei);
                    }
                };

            MessageBox.Show(gwl(20,100) + ""); //打印结果为 "70"
   
        }

    }


  不错不错,写到这里,我对lambda表达式突然开窍了。

  上面这些例子,好好理解下,下面我继续摘抄张同学的,介绍一个系统指定的Func<T>委托。

Func<T>委托

   T 是泛型的参数类型,这是一个泛型类型的委托,用起来比较方便

   先上例子

    public class VMLambdaStudyDemo1
    {
        
        public void Btn_LambdaDemo1_Click()
        {
            Func<int, string> gwl = p => p + 10 + "--返回类型为string类型";
            MessageBox.Show(gwl(10)); //打印结果为 "20--返回类型为string类型"
   
        }

    }

   说明,这里可以看到,p为int类型参数,然而lambda主体返回的是string类型的,你看我的MessageBox.Show()里面都不用加 “” 了。

   我勒个去,好简单的说。

   再上一个例子

        public void Btn_LambdaDemo1_Click()
        {
            Func<int, int, bool> gwl = (p, j) =>
                {
                    if ((p + j) == 10)
                    {
                        return true;
                    }
                    return false;
                };
            MessageBox.Show(gwl(5,5) + ""); //打印结果为 "True"
   
        }

   说明:从这个例子,我们能看到,p为int类型,j为int类型,返回值为bool类型。

   看完上面的两个例子,相信大家应该明白Func<T>的用法:多个参数,前面的为委托方法的参数,最后一个参数为委托方法的返回类型。不得不说,这个张同学的思路讲的真清晰啊!

lambda表达式总结

      ① “lambda表达式”是一个匿名函数,它可以包含表达式和语句,并且可用于创建委托。

      ② lambda表达式格式为:(参数列表) => 表达式或语句块

      ③ 说白了,lambda表达式和匿名方法其实是一件事,它们的作用都是:产生方法。即内联方法。

      ④  lambda不仅可用于创建委托,还可以用于创建表达式目录树。下一小节就讲lambda表达式树!

lambda表达式树

   注意上面标题是“lambda表达式树”,而不是“lambda表达式”!

   ● Expressions Tree概念

       ① 表达式树又称为表达式目录树,以数据形式表示语言级代码。所有的数据都存储在树结构中,每个节点表示一个表达式。

        ② Lambda表达式树允许我们像处理数据(比如读取,修改)一样来处理 lambda表达式。(注意这里可以看出lambda表达式树和lambda表达式的关系!)

   ● 表达式的种类(这里可以先了解一下,随着实验的深入返过来再看理解的会更深)

        ① 参数表达式:ParameterExpression. 就是一个方法中的参数,例如   search(string key)中的key可以看成是一个参数表达式;再例如lambda表达式中 (n => n * 3)中的n也可以看成是一个参数表达式。

        ② 二次元表达式:BinaryExpression. 例如 a + b 或者 (n * 3) < 5 等等。

        ③ 方法调用表达式:MethodCallExpression,例如自定义LINQ提供程序中实现Orderby的操作。

        ④ 常数表达式:ConstantExpression. 例如数值5

        ⑤ 带有条件运算的表达式:ConditionExpression

   还有很多,可以去看MSDN,不解释。

   下面举个简单的例子,还是采用断点的方式追踪 

    

        public void Btn_LambdaDemo1_Click()
        {
            Func<int, bool> test = n => (n * 3) < 5;

            Expression<Func<int, bool>> filter = n => (n * 3) < 5;        
                      
        }
    从程序可以看出,test和filter均用到的 lambda表达式 n => (n * 3) < 5,但是 filter 利用Expression将lambda表达式以目录树的形式表示为数据结构,下面咱们运行程序,看看这两个到底有什么不同。

    程序运行到第一个断点处

WPF-MVVM形式学习笔记4——Lambda表达式学习

   此时 test 和 filter均为空,两次F5,程序运行到第3个断点处,展开test和filter,如下图

WPF-MVVM形式学习笔记4——Lambda表达式学习

  从上图中可以看到,两者的内容大不一样,其中filter里面包含了很多 lambda表达式 (n => (n*3)<5) 的信息,比如Body主体是 (n*3)<5,返回类型ReturnType为Boolen类型,有1个参数。展开filter的Body,如下图

WPF-MVVM形式学习笔记4——Lambda表达式学习

   可以看到Body里面还可以看到主体左边为 n * 3 ,主体右边为 5,节点类型为 < 即LessThan等等,信息量好大的说。

   下面通过函数直观的展现表达式树的内部信息

   

    public class VMLambdaStudyDemo1
    {
        
        public void Btn_LambdaDemo1_Click()
        {
            Func<int, bool> test = n => (n * 3) < 5;

            Expression<Func<int, bool>> filter = n => (n * 3) < 5;
            BinaryExpression lt = (BinaryExpression)filter.Body;
            BinaryExpression mult = (BinaryExpression)lt.Left;
            ParameterExpression en = (ParameterExpression)mult.Left;
            ConstantExpression three = (ConstantExpression)mult.Right;
            ConstantExpression five = (ConstantExpression)lt.Right;
            var One = filter.Compile();
            MessageBox.Show(One(5) + " || " + One(1) + " || " 
                        + lt.NodeType  + " || " + mult.NodeType
                         + " || " + en.Name + " || " + 
                         three.Value + " || " + five.Value);
                      
        }

    }
     可以自己断点追踪一下看看,我就不演示了,截图好麻烦的说。下面是程序的运行结果

WPF-MVVM形式学习笔记4——Lambda表达式学习

    上面的例子可以直观的展示 “ Lambda表达式树允许我们像处理数据(比如读取,修改)一样来处理 lambda表达式”这一句话,再来个例子,加深印象。

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

namespace LambdaDemo.ViewModel
{

    public class VMLambdaStudyDemo1
    {
        
        public void Btn_LambdaDemo1_Click()
        {
            ParameterExpression a = Expression.Parameter(typeof(int), "i"); //创建一个表达式树中的参数,作为一个节点,这里是最下层的节点
            ParameterExpression b = Expression.Parameter(typeof(int), "j");
            BinaryExpression be = Expression.Multiply(a, b);  //这里i*j,生成表达式树中的一个节点,比上面节点高一级

            ParameterExpression c = Expression.Parameter(typeof(int), "w");
            ParameterExpression d = Expression.Parameter(typeof(int), "x");
            BinaryExpression be1 = Expression.Multiply(c, d);

            BinaryExpression su = Expression.Add(be, be1);   //运算两个中级节点,产生终结点

            Expression<Func<int, int, int, int, int>> lambda = Expression.Lambda<Func<int, int, int, int, int>>(su, a, b, c, d);

            Func<int, int, int, int, int> f = lambda.Compile();  //将表达式树描述的lambda表达式,编译为可执行代码,并生成该lambda表达式的委托;

            System.Windows.MessageBox.Show("lambda:" + lambda + "Result:" + f(1, 1, 1, 1));
        }

    }

}

运行结果为

WPF-MVVM形式学习笔记4——Lambda表达式学习

上段代码的lambda表达式树的结构图,直接用张同学的了

WPF-MVVM形式学习笔记4——Lambda表达式学习

结合这张图再看代码,仔细理解下,应该能理解的比较透彻了。

结语

   我勒个去,敲了这么长,比敲代码累多了,敲到这里,我对lambda算是比较理解了,但是反思一下,会用吗?答曰:不会!

   不过没事儿,边学边用,下一篇准备研究一下 NotificationObject里的  protected void RaisePropertyChanged<T>(Expression<Func<T>> propertyExpression)