【C#学习笔记】匿名函数和lambda表达式

文章目录


匿名函数

为什么我们要使用匿名函数?匿名函数存在的意义是为了简化一些函数的定义,特别是那些定义了之后只会被调用一次的函数,与其大费周折拖动文件然后定义在类中的某个位置,匿名函数将更加方便简洁。

匿名函数的定义

以下两种无参数匿名函数:

csharp 复制代码
Action A = delegate { Debug.Log("Hello"); };
A += delegate () 
{
   Debug.Log("Hello"); 
};

匿名函数声明需要配合委托使用,并且声明时需要在函数头加上delegate,匿名函数的无参构造可以省略括号。

匿名函数定义不允许使用泛型(很好理解,泛型是为了函数调用时能够灵活地接受不同类型的参数,但是使用匿名函数意味着它只会被调用一次,与其使用泛型不如让我们直接指定)

csharp 复制代码
Action<int, string> A = delegate (int i, string s)
{
    int q = Int32.Parse(s) + i;
};

定义带参数的匿名函数只需像正常函数定义即可。如果需要返回值只需使用Func

csharp 复制代码
Func<int, string,int> A = delegate (int i, string s)
{
    int q = Int32.Parse(s) + i;
    return q;
};

匿名函数作为参数传递

现有如下定义:

csharp 复制代码
    class Test
    {
        public Action action;
        public void Dosomething(int a ,Action fun)
        {
            fun();
        }
        public Action MyFun()
        {
            return delegate () { Debug.Log("返回委托类型的匿名函数"); };
        }
    }

在上述类中,我们定义了两个方法,Dosomething需要传入一个Action委托funMyfun则返回一个Action类型的返回值(定义的委托是可以作为返回值类型的),在其中我们将一个匿名函数作为委托类型的返回值。

csharp 复制代码
    void Start()
    {
        Test t = new Test();
        t.Dosomething(1, delegate { Debug.Log("匿名函数作为参数传入"); });
        Action A = delegate { Debug.Log("委托装载匿名函数并作为参数传入)"); };
        t.Dosomething(1, A);
    }

匿名函数可以作为委托类型的参数传入。也可以返回时作为委托类型(猜测应当是匿名函数作为参数传递时自动被封装为了对应委托):

csharp 复制代码
    void Start()
    {
        Test t = new Test();
        t.action = t.MyFun();
        t.action(); // 输出:返回委托类型的匿名函数
        t.MyFun()(); // 只有委托返回值类型才能这么使用,调用函数后再加个括号直接调用委托
    }

委托类型返回值的函数在调用后再加上括号可以直接调用该委托。


匿名函数的缺点

使用匿名函数的最大优点就是方便,但是匿名函数最大的缺点就是匿名,如下所示:

csharp 复制代码
Action A = delegate { Debug.Log("你好"); };
A -= delegate { Debug.Log("你好"); }; // 无效
A();// 依旧会输出你好,因为匿名函数无法删除
A = null; // 只有清空A才能从中删除匿名函数

想要删除委托中的匿名函数,即使我们在A中减去相同定义的匿名函数也无济于事,因为此函数非彼函数。匿名函数没有函数名,也就无法被减去,即使我们定义了相同的匿名函数,他们的地址本质上也不是同一个函数。

所以如果一个委托只存一个函数,那就可以使用匿名函数,但是如果一个委托存在多个函数,那么当你想要去除这个匿名函数的时候,就只能清空这个委托。所以在设计使用匿名函数的时候,要么用一个委托只存储匿名函数,要么作为参数传入委托类型的返回值的函数进行调用(就像上述的t.MyFun()();)。


lambda表达式

什么是lambda表达式

lambda表达式就是匿名函数的一种创建方式,使用lambda表达式可以省略匿名函数的delegate定义:

csharp 复制代码
Action A = () => { Debug.Log("你好"); };

lambda表达式省略了delegate关键字,并使用 lambda 声明运算符=>来声明。 同理,有参和有返回的lambda表达式也和匿名函数的定义相同。

csharp 复制代码
        Action<int, string> B = (int i, string s) =>
        {
            int q = Int32.Parse(s) + i;
        };
        Func<int, string,int> C = (int i, string s) =>
        {
            int q = Int32.Parse(s) + i;
            return q;
        };
        // 委托已经指定类型,可以省略参数类型
        Func<int, string,int> C = (i, s) =>
        {
            int q = Int32.Parse(s) + i;
            return q;
        };

当lambda表达式只执行一个语句的时候,连花括号都不需要了, 使用空括号指定0个输入参数:

csharp 复制代码
Action line = () => Console.WriteLine();

不带返回值类型的lambda表达式的简洁用法,单个参数可以省略小括号:

csharp 复制代码
Action<int> square = x => Console.WriteLine("x");

甚至lambda表达式可以省略返回值定义,只需定义委托类型即可:

csharp 复制代码
Func<int, int> square = x => x * x; // return x*x,在带返回值类型的委托中省略return
Func<int, int, bool> testForEquality = (x, y) => x == y; //多个入参还是需要括号和逗号区分的

在多个入参的情况下,lambda用弃元来表示哪些参数不被使用

(多嘴一下,在委托定义了入参数量,然后lambda表达式需要使用到的入参比定义的入参数量少的情况下当然可以使用弃元。但是作为添加到委托内的匿名函数,如果委托需要装载多个函数的话,实际上不太适合使用匿名函数):

csharp 复制代码
Func<int, int, int> constant = (_, _) => 42;

闭包

关于闭包的具体说明已经在此文【Lua学习笔记】Lua进阶------函数和闭包中解释过了,此处便不再赘述。

简单来说,闭包就是内部函数引用了外部函数的变量,导致本应该在栈中释放的外部函数的生命周期遭到改变(延长)。这个(这些)变量被我们称为upvalues,它们原本的作用域是外部函数,但是随着内部函数的使用,它们的作用域又包括了内部函数。

就如同下面的例子:

csharp 复制代码
public event Action action;
public void Test()
{
    int value = 100;
    action = () => { Debug.Log(value); };//使用了value,但value作用域在函数Test而非匿名函数内
}

上述例子中我们在匿名函数内部使用了外部函数Test的变量value,结果是可以正常执行的。照理说局部变量valueTest中没被使用就会释放,但是匿名函数的使用延长了它的生命周期,这就是闭包。

再看下列的例子:

csharp 复制代码
public void Test()
{
    for (int i = 0; i < 10; i++)
    {
        action += () => { Debug.Log(i); };
    }
}
action();

当我们执行上述语句的时候输出答案是什么?可能你以为是0123456789,但真正的答案是输出10次10,原因其实也很简单,在委托中存储了十个匿名函数,每个的指令是输出i,但是不要忘了i是upvalue,它不是赋值到内部这个匿名函数的局部变量,而是匿名函数直接调用在外部函数的i的值,当循环结束时i=10,本应当释放的i的生命周期得到了延长,当我们执行委托的时候,则匿名函数才会调用这个i,而此时i=10,因此输出了10次10。

csharp 复制代码
public void Test()
{
    for (int i = 0; i < 10; i++)
    {
    	int index = i;
        action += () => { Debug.Log(index); };
    }
}
action();

然而使用int index = i则可以实现上述功能,原因在于它是值类型的变量,我们每次在for循环中int一个index,实际上都是声明了一个新的int变量。所以每个匿名函数调用的index实际上是10个不同的index。

如果害怕lambda表达式不小心捕获了外部函数的upvalue,则可以使用static关键字进行限制:

csharp 复制代码
Func<double, double> square = static x => x * x;
相关推荐
无所谓จุ๊บ1 小时前
树莓派开发扩展十二 -C#编写客户端控制小车 Xamarin.Forms
c#·xamarin
Lx3525 小时前
C# 一分钟浅谈:GraphQL API 与 C#
后端·c#
AitTech6 小时前
C#动态类型详解:应用场景与注意事项
开发语言·c#
程序leo源10 小时前
C语言:操作符详解1
android·java·c语言·c++·青少年编程·c#
yngsqq13 小时前
c#注册机制作(根据机器码生成注册码和注册文件)
c#
雯0609~14 小时前
c#:winform调用bartender实现打印(学习整理笔记)
开发语言·c#
股票GPT分析17 小时前
《Python 股票交易分析:开启智能投资新时代》(二)
大数据·服务器·python·c#·fastapi
j1780505690617 小时前
C#学习笔记——窗口停靠控件WeifenLuo.WinFormsUI.Docking使用-腾讯云开发者社区-腾讯云
开发语言·c#
离歌漠21 小时前
C#调用C++ DLL方法之C++/CLI(托管C++)
c++·c#·clr
sukalot21 小时前
windows C#-异步返回类型(下)
windows·c#