16.委托和事件-lambda表达式
16.1 知识点
什么是lambda表达式
可以将lambda表达式理解为匿名函数的简写。它除了写法不同外,使用上和匿名函数一模一样,都是和委托或者事件配合使用的。
lambda表达式语法
// lambda表达式
// (参数列表) =>
// {
// // 函数体
// };
// 匿名函数
// delegate (参数列表)
// {
// };
lambda表达式的使用
主函数内
无参无返回值的lambda表达式
Action action1 = () =>
{
Console.WriteLine("无参无返回值的lambda表达式");
};
action1(); // 无参无返回值的lambda表达式
有参无返回值的lambda表达式
Action<int> action2 = (int value) =>
{
Console.WriteLine("有参无返回值的lambda表达式 int value:{0}", value);
};
action2(100); // 有参无返回值的lambda表达式 int value:100
有参无返回值的lambda表达式省略参数类型的写法
参数类型可以省略,参数类型和委托或事件容器一致。
Action<int> action3 = (value) =>
{
Console.WriteLine("省略参数类型的写法 value:{0}", value);
};
action3(200); // 省略参数类型的写法 value:200
有参有返回值的lambda表达式
Func<string, int> action4 = (value) =>
{
Console.WriteLine("有参有返回值的lambda表达式 value:{0}", value);
return 1;
};
Console.WriteLine(action4("123123"));
// 有参有返回值的lambda表达式 value:123123
// 1
// 其它传参使用等和匿名函数一样
// 缺点也是和匿名函数一样的,添加到委托后没有办法指定移除lambda表达式
闭包
闭包的基本概念
- 闭包是指一个函数(或者委托)以及其相关的引用环境,形成了一个封闭的作用域。闭包能够捕获并维持其所在作用域的状态,即使在该作用域外部调用闭包,它也能够访问和修改这些状态。
- 重点:内层的函数可以引用包含在它外层的函数的变量,即使外层函数的执行已经终止。
- 注意:该变量提供的值并非变量创建时的值,而是在父函数范围内的最终值。
- 在 C# 中定义一个函数,而在这个函数内部又使用了外部的变量时,这个函数就可以形成一个闭包。闭包简单来说就是一个函数连同它用到的外部变量一起打包了起来。
- 让我们通过一个通俗易懂的例子来说明闭包:
using System;
class Program
{
static Func<int, int> Counter(int increment)
{
int count = 0; // 外部变量
Func<int, int> incrementCounter = (number) =>
{
count += increment; // 在函数内部使用了外部变量
return number + count;
};
return incrementCounter;
}
static void Main(string[] args)
{
// 创建一个计数器,增量为 5
var counter = Counter(5);
// 使用计数器,初始数字为 10
Console.WriteLine(counter(10)); // 输出 15
// 再次使用计数器,初始数字为 20
Console.WriteLine(counter(20)); // 输出 25
}
}
在这个例子中,
Counter
函数返回了一个委托(incrementCounter
),这个委托内部引用了外部的变量count
。虽然Counter
函数已经执行完毕,但是incrementCounter
依然可以访问并修改Counter
函数中的count
变量。这就是闭包的作用,它保留了函数定义时的环境,使得函数在之后的执行中能够访问和修改它所引用的外部变量。在这个例子中,闭包是在
Counter
函数内部定义的匿名方法,即incrementCounter
。这个匿名方法捕获了Counter
函数中的局部变量count
,并形成了闭包。因此,无论Counter
函数执行完毕后,返回的委托incrementCounter
仍然可以访问和修改count
变量,这就是闭包的特性。
class语句块外 namespace语句块内
// 测试类
class Test
{
// 事件
public event Action myEvent;
public Test()
{
// 构造函数的临时变量 正常来说构造函数结束他就会被释放
int value = 10;
// 这里就形成了闭包
// 因为当构造函数执行完毕时,其中申明的临时变量value的声明周期被改变了
// value还有作用不会被释放,一直会存到action里面,除非手动把action置空
myEvent = () =>
{
Console.WriteLine(value);
};
for (int i = 0; i < 10; i++)
{
// 注意:该变量提供的值并非变量创建时的值,而是在父函数范围内的最终值。
// 此index 非彼index 每次for循环的index都是新的临时变量
int index = i;
myEvent += () =>
{
Console.WriteLine("i:" + i);
// 这里的会一直会输出 i:10
// 本质上传的是i变量,这个i变量在事件没有执行时一直没用到,一直存在事件容器里
// 但他真正用的时候,这个i的最终值已经是10了,所以只会输出 i:10
// i在父函数范围内(Test这个函数的语句块)的最终值是10
Console.WriteLine("index:" + index);
// 这里会逐个输出index:0-9
// 因为每次for循环的index都是新的临时变量,所以一共会有10个index临时变量
// 所以保存的index都不同
// index在父函数范围内(每一次for循环的语句块)的最终值就是int index = i时的赋值
};
}
}
// 执行事件
public void DoEvent()
{
myEvent();
}
}
i 的值为 10:
i
是 for 循环中的变量,在每次循环迭代时都会被更新。但是在匿名方法中,i
是通过闭包捕获的,它捕获的是变量i
的引用,而不是值的拷贝。- 当
myEvent
被调用时,所有被捕获的i
都指向同一个内存位置,它们都引用了最后一次迭代的值,也就是10
。 - 因此,每次调用
myEvent
时,打印的i
的值都是10
。
index 的值为每次迭代的值:
- 在每次循环迭代时,会为
index
创建一个新的局部变量,并在匿名方法中捕获了该变量的值。 - 每个匿名方法捕获的
index
都是不同的变量实例,它们具有不同的内存位置和值。 - 因此,每次调用
myEvent
时,打印的index
的值会是对应迭代时的值,即0
到9
。
主函数内
// 调用构造函数,临时变量value的生命周期被改变了
Test test = new Test(); // 10
test.DoEvent();
// i: 10
// index: 0
// i: 10
// index: 1
// i: 10
// index: 2
// i: 10
// index: 3
// i: 10
// index: 4
// i: 10
// index: 5
// i: 10
// index: 6
// i: 10
// index: 7
// i: 10
// index: 8
// i: 10
// index: 9
总结
匿名函数的特殊写法就是lambda表达式。固定写法是(参数列表)=>{}
,参数列表可以直接省略参数类型。主要在委托传递和存储时,为了方便可以直接使用匿名函数或者lambda表达式。缺点是无法指定移除。
16.2 知识点代码
using System;
namespace Lesson15_lambda表达式
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine("lambda表达式");
#region 知识点一 什么是lambda表达式
//可以将lambda表达式 理解为匿名函数的简写
//它除了写法不同外
//使用上和匿名函数一模一样
//都是和委托或者事件 配合使用的
#endregion
#region 知识点二 lambda表达式语法
//lambda表达式
//(参数列表) =>
//{
// //函数体
//};
//匿名函数
//delegate (参数列表)
//{
//};
#endregion
//主函数内
#region 知识点三 lambda表达式的使用
//1.无参无返回值的lambda表达式
Action action1 = () =>
{
Console.WriteLine("无参无返回值的lambda表达式");
};
action1();//无参无返回值的lambda表达式
//2.有参无返回值的lambda表达式
Action<int> action2 = (int value) =>
{
Console.WriteLine("有参无返回值的lambda表达式 int value:{0}", value);
};
action2(100);//有参无返回值的lambda表达式 int value:100
//3.有参无返回值的lambda表达式省略参数类型的写法 参数类型可以省略 参数类型和委托或事件容器一致
Action<int> action3 = (value) =>
{
Console.WriteLine("省略参数类型的写法 value:{0}", value);
};
action3(200);//省略参数类型的写法 value:200
//4.有参有返回值的lambda表达式
Func<string, int> action4 = (value) =>
{
Console.WriteLine("有参有返回值的lambda表达式 value:{0}", value);
return 1;
};
Console.WriteLine(action4("123123"));
//有参有返回值的lambda表达式 value:123123
//1
//其它传参使用等和匿名函数一样
//缺点也是和匿名函数一样的 添加到委托后没有办法指定移除lambda表达式
#endregion
#region 知识点四 闭包
//调用构造函数 临时变量value的生命周期被改变了
Test test = new Test();//10
test.DoEvent();
//i: 10
//index: 0
//i: 10
//index: 1
//i: 10
//index: 2
//i: 10
//index: 3
//i: 10
//index: 4
//i: 10
//index: 5
//i: 10
//index: 6
//i: 10
//index: 7
//i: 10
//index: 8
//i: 10
//index: 9
#endregion
}
}
//class语句块外 namespace语句块内
#region 知识点四 闭包
//闭包的基本概念
//内层的函数可以引用包含在它外层的函数的变量
//即使外层函数的执行已经终止
//注意:该变量提供的值并非变量创建时的值,而是在父函数范围内的最终值。
//测试类
class Test
{
//事件
public event Action myEvent;
public Test()
{
//构造函数的临时变量 正常来说构造函数结束他就会被释放
int value = 10;
//这里就形成了闭包
//因为 当构造函数执行完毕时 其中申明的临时变量value的声明周期被改变了
//value还有作用不会被释放 一直会存到action里面 除非手动把action置空
myEvent = () =>
{
Console.WriteLine(value);
};
for (int i = 0; i < 10; i++)
{
//注意:该变量提供的值并非变量创建时的值,而是在父函数范围内的最终值。
//此index 非彼index 每次for循环的index都是新的临时变量
int index = i;
myEvent += () =>
{
Console.WriteLine("i:" + i);
//这里的会一直会输出 i:10
//本质上传的是i变量 这个i变量在事件没有执行时一直没用到 一直存在事件容器里
//但他真正用的时候 这个i的最终值已经是10了 所以只会输出 i:10
//i在父函数范围内(Test这个函数的语句块)的最终值是10
Console.WriteLine("index:" + index);
//这里会逐个输出index:0-9
//因为每次for循环的index都是新的临时变量 所以一共会有10个index临时变量
//所以保存的index都不同
//index在父函数范围内(每一次for循环的语句块)的最终值就是int index = i时的赋值
};
}
}
//执行事件
public void DoEvent()
{
myEvent();
}
}
#endregion
//总结
//匿名函数的特殊写法 就是 lambda表达式
//固定写法 就是 (参数列表)=>{}
//参数列表 可以直接省略参数类型
//主要在 委托传递和存储时 为了方便可以直接使用匿名函数或者lambda表达式
//缺点:无法指定移除
}
16.3 练习题
有一个函数,会返回一个委托函数,这个委托函数中只有一句打印代码,之后执行返回的委托函数时,可以打印出1~10
class语句块内 主函数外
// 要用到闭包的知识
// lambda表达式
static Action GetFun()
{
Action action = null;
for (int i = 1; i <= 10; i++)
{
int index = i;
action += () =>
{
Console.WriteLine(index);
};
}
return action;
}
// 匿名函数
static Action GetFun2()
{
Action action = null;
for (int i = 1; i <= 10; i++)
{
int index = i;
action += delegate ()
{
Console.WriteLine(index);
};
}
return action;
}
// 调用别的函数包裹起来
static Action GetFun3()
{
Action action = null;
for (int i = 1; i <= 10; i++)
{
int index = i;
// action += Show;
// 报错 首先action是无参无返回值 Show方法有参无返回值 不匹配
// 其次 计算action能够匹配Show 也无法传入参数 因为只是传函数名
action += () =>
{
Show(index);
};
}
return action;
}
// 打印函数
static void Show(int i)
{
Console.WriteLine(i);
}
主函数内
GetFun()();
GetFun2()();
GetFun3()();
// 都能正常打印出1~10
// 1
// 2
// 3
// 4
// 5
// 6
// 7
// 8
// 9
// 10
16.4 练习题代码
using System;
namespace Lesson15_练习题
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine("lambda表达式 练习题");
//主函数内
#region 练习题一
GetFun()();
GetFun2()();
GetFun3()();
//都能正常打印出1~10
//1
//2
//3
//4
//5
//6
//7
//8
//9
//10
#endregion
}
//class语句块内 主函数外
#region 练习题一
//有一个函数,会返回一个委托函数,这个委托函数中只有一句打印代码
//之后执行返回的委托函数时,可以打印出1~10
//要用到闭包的知识
//lambda表达式
static Action GetFun()
{
Action action = null;
for (int i = 1; i <= 10; i++)
{
int index = i;
action += () =>
{
Console.WriteLine(index);
};
}
return action;
}
//匿名函数
static Action GetFun2()
{
Action action = null;
for (int i = 1; i <= 10; i++)
{
int index = i;
action += delegate ()
{
Console.WriteLine(index);
};
}
return action;
}
//调用别的函数包裹起来
static Action GetFun3()
{
Action action = null;
for (int i = 1; i <= 10; i++)
{
int index = i;
//action += Show;
//报错 首先action是无参无返回值 Show方法有参无返回值 不匹配
//其次 计算action能够匹配Show 也无法传入参数 因为只是传函数名
action += () =>
{
Show(index);
};
}
return action;
}
//打印函数
static void Show(int i)
{
Console.WriteLine(i);
}
#endregion
}
}
转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 785293209@qq.com