大多数人,都低估了编程学习的难度,而高估了自己的学习能力和毅力。
当前系列: C#语法 修改讲义

复习:J&C:Lambda表达式


委托

什么是委托(delegate)?

  • 委托是一种(特殊的)类型
  • 代表的是方法(的引用),所以需要指定参数和返回值
        //像声明一个类一样声明一个委托
        //但定义了这个委托的返回void类型,接受两个int类型的参数
        delegate void Calculate(int a, int b);
  • 委托类型的变量可以像方法一样使用
                //把方法(不是方法的返回值)直接赋值给一个Calculate委托变量
                Calculate ai = Add;
                //类似于调用Add()方法一样调用delegateAdd
                ai(3, 7);

另外,委托类型的变量可以和普通变量一样的:被赋值、作为方法参数传递、作为方法返回值。

委托可以使用泛型

delegate T growHandler<T>(bool isAdult);


Func和Action

而且,为了便于Lambda表达式的传递,.NET为我们预置了两种委托:

  • Func:有返回值的方法。使用泛型参数依次表示方法参数,最后一个表示方法返回值
  • Action:没有返回值的方法。使用泛型参数依次表示方法参数

Func和Action为我们提供了最多16个参数,怎么都够用了!^_^

@想一想@:如果说16个参数还不够怎么办?

而且这两种委托都是泛型的,所以我们基本上都不再自己声明委托了。

public delegate TResult Func<in T, out TResult>(T arg);
public delegate void Action<in T>(T obj);


匿名方法和Lambda表达式

最初,我们每给委托一个值就需要声明一个方法,比较麻烦。于是C#很快就推出了:匿名方法。
            Calculate ai = delegate (int x, int y)
            {
                Console.WriteLine($"just a piece of cake: {x}/{y}={x/y}");
            };
            ai(8, 3);

语法特点:

  • 找不到方法名
  • 由关键字delegate代替
  • 匿名方法不需要标志返回值类型(演示:略)

注意:参数不能标记为ref/out/in的引用传递参数除外

将匿名方法的delegate去掉,使用箭头(=>)替代,就是Lambda表达式了。

    Calculate ai = /*delegate*/ (int x, int y) =>  /*添加=>*/
    {
        Console.WriteLine($"just a piece of cake: {x}/{y}={x / y}");
    };

lambda表达式就会产生闭包


事件(event)

委托是事件的基础,事件又对委托进行了封装……

在Winform和以前的WebForm中,事件被大量使用,是必须掌握的知识(但MVC不是事件驱动的),一个完整的事件流程包括:

控件(button)开发人员(微软)定义一个Button类,Button类定义/声明/暴露了一个OnClick事件,可供外部“订阅”(使用)

public class Button
{
    //定义事件,用于发布
    public event EventHandler OnClick;    //注意:EventHnadler是一个委托

控件的使用人员:

//实例化一个发布了OnClick事件的button
Button btn = new Button();
//事件被btn_click订阅,即:当这个btn被click时调用btn_click()方法
btn.OnClick += btn_click;

注意:event和delegate都可以被多次订阅(+=)/退订(-=),演示:略

btn_click是一个方法
private static void btn_click(object sender, EventArgs e)
{
    //应用开发人员真正要写代码的地方……
    Console.WriteLine("点我干啥呢?");

实际上如果你使用WinForm或WebForm,截止到现在btn_click()方法生成,都是框架自动完成的,你只需要在btn_click()中写你自己的代码就行了。


Expression

后面的学习中,同学们可能会看到这种代码:

Expression<Func<int, int>> ef = i => i * i;
Lambda表达式赋值给的不是Func,而是

Expression<Func>

这被称之为“表达式(树)”。和Func的区别:

  • Expression<Func<T>>:代表一个树状结构/容器,里面存放的是Func,还需要进一步的操作(比如,调用Compile())才能把表达式变成一个方法
    ef.Compile()
  • Func<T>:代表一个委托,一个方法的引用,可以直接当做方法调用

Expression的作用

在运行时动态的生成表达式

#理解:什么是表达式?#

由变量、数值(或常量)、运算符、函数等组合起来,能够据此进行运算得到结果的“式子”。比如:

3                            //常数表达式
a                            //变量或参数表达式
!a                           //一元逻辑非表达式
a + b                        //二元加法表达式
Math.Sin(a)                  //方法调用表达式
new StringBuilder()          //new 表达式

到目前为止,表达式(不是表达式的输入和运行结果)都是静态的(由源代码写死的),在运行时没法改变。

比如,我们现有一个func

Func<int, int> func = i => i + 1;

想要基于func再生成表达式:(i + 1) * 2,这样写是不行的:(演示)

func = i => func(i) * 2;
但有了Expression就可以,它不仅仅是存储表达式的容器,还可以生成表达式:
  1. 准备一个类型为int,名为i的parameter
    ParameterExpression param = Expression.Parameter(typeof(int), "i");
  2. 生成一个能将输入的parameter和常量1相加的表达式:
    Expression expression = Expression.Add(
        param,
        Expression<int>.Constant(1)
        );
  3. 将expression转换成lambda表达式”参数的泛型Expression
    Expression<Func<int, int>> lambda = Expression.Lambda<Func<int, int>>(expression, param);

而且还可以对已有表达式进行“组装”,比如在上述第2步之后,还可以将就之前的expression,进一步的运算:

expression = Expression.Multiply(
    expression,
    Expression<int>.Constant(2)
    );

演示:此时的expression就是:((i+1)*2)

为什么是颗树?【选】

复杂的表达式可以建构成一个树结构,比如:3+2-5*0:


Lambda表达式(准确的说,是Expresion Lambda,即lambda表达式=>右侧的部分)是创建“表达式树”的最精炼(concise)的元素。


作业

见:J&C:Lambda表达式

学习笔记
源栈学历
今天学习不努力,明天努力找工作

作业

觉得很 ,不要忘记分享哟!

任何问题,都可以直接加 QQ群:273534701

在当前系列 C#语法 中继续学习:

多快好省!前端后端,线上线下,名师精讲

  • 先学习,后付费;
  • 不满意,不要钱。
  • 编程培训班,我就选源栈

更多了解 加:

QQ群:273534701

答疑解惑,远程debug……

B站 源栈-小九 的直播间

写代码要保持微笑 (๑•̀ㅂ•́)و✧

公众号:源栈一起帮

二维码