C#进阶学习(十)更加安全的委托——事件以及匿名函数与Lambda表达式和闭包的介绍

目录

第一部分:事件

一、什么是事件?

关键点:

二、事件的作用

三、事件怎么写以及注意事项

访问修饰符 event 委托类型 事件名;  

四、事件区别于委托的细节之处

第二部分:匿名函数 

一、什么是匿名函数

 二、匿名函数的基本申明规则以及使用示例

三、匿名函数的优缺点

优点:

缺点:

第三部分:Lambda表达式

一、什么是 Lambda 表达式?

关键特性:

二、Lambda 表达式的语法

三、Lambda 表达式的使用示例

1. 有参有返回(显式类型)

2. 有参有返回(显式类型,语句体)

3. 无参有返回

4. 无参无返回

5. 不显式声明类型(类型推断) 

四、什么是闭包

1. 闭包的基本原理

2. 闭包捕获的是变量,不是值

示例 2:闭包与循环陷阱

3. 如何避免循环陷阱?

修复示例:

4. 闭包的常见应用场景

5.闭包的注意事项

第四部分:委托,事件,匿名函数的总结 


第一部分:事件

一、什么是事件?

        事件(Event)是 C# 中一种特殊的委托(Delegate),用于实现发布-订阅模型(Publish-Subscribe Pattern)。它允许一个对象(发布者)在特定动作发生时通知其他对象(订阅者)。事件的核心思想是封装委托,提供更安全的访问控制。

事件是基于委托的存在
事件是委托的安全包裹
让委托的使用更具有安全性
事件是一种特殊的变量类型

关键点:
  1. 本质:事件是对委托的封装,确保只有声明事件的类才能触发(Invoke)它。

  2. 设计模式:事件是观察者模式(Observer Pattern)的具体实现。

  3. 语法:事件基于委托类型定义,但通过 event 关键字声明。

二、事件的作用

  1. 解耦通信
    事件允许对象之间通过松耦合的方式通信。例如,GUI 中的按钮点击事件不需要知道具体哪个类会处理点击逻辑。

  2. 安全性
    通过限制外部对委托的直接访问(如触发或重置委托链),防止误操作。

  3. 多播支持
    事件天然支持多播(多个订阅者),例如多个方法可以同时订阅同一个事件。

  4. 标准化设计
    事件常用于框架设计(如 WinForms、WPF、ASP.NET),提供统一的交互模式。

三、事件怎么写以及注意事项

事件的使用:
1.事件是作为 成员变量存在于类中
2.委托怎么用 事件就怎么用
事件相对于委托的区别:
1.不能在类外部 赋值
2.不能在类外部 调用
注意
他只能作为成员存在于类和接口以及结构体中

1. 事件的基本声明规则
事件的声明需遵循以下规则:

  • 委托类型:事件必须基于一个已定义的委托类型(如 EventHandler 或自定义委托),不能直接指定返回值类型。

  • 语法格式

访问修饰符 event 委托类型 事件名;  

  • 例如:public event Action Clicked;(若委托类型为 Action),当然也可以自定义委托类型。

  • 触发权限:只有声明事件的类可以触发(调用)事件。

  • 订阅限制:外部代码只能通过 += 订阅、-= 取消订阅,不能直接赋值(如 = null)。

 2.事件的实际代码示例:

        注意?.的用法,首先判断左边类型是否为空,不为空则唤醒即执行,为空则不执行右边。相当于是更加安全的使用了委托

using System;

// 1. 定义委托类型(简短示例)
public delegate void Notify();  // 无参数、无返回值的委托

// 2. 声明包含事件的类
public class EventDemo
{
    // 声明事件(基于 Notify 委托)
    public event Notify OnEvent;

    // 触发事件的方法(Trigger)
    public void Trigger()
    {
        OnEvent?.Invoke();  // 安全调用
    }
}

// 3. 订阅事件的类
public class Subscriber
{
    // 事件处理方法(简短方法名:Log)
    public void Log()
    {
        Console.WriteLine("事件已触发!");
    }
}

// 4. 主函数中的使用
public class Program
{
    public static void Main()
    {
        EventDemo demo = new EventDemo();
        Subscriber sub = new Subscriber();

        // 订阅事件
        demo.OnEvent += sub.Log;

        // 触发事件(由 EventDemo 类内部控制)
        demo.Trigger();
    }
}

四、事件区别于委托的细节之处

特性委托(Delegate)事件(Event)
访问权限公共成员,外部可直接调用或赋值封装后的成员,外部只能通过 += 和 -= 订阅
触发权限任何持有委托引用的类均可触发仅声明事件的类可触发
多播安全性外部可重置委托链(如 = null外部只能追加或移除方法
设计用途通用回调机制,灵活但需手动管理标准化发布-订阅模型,安全性更高
典型应用场景回调方法、LINQ 查询GUI 交互、消息通知系统

对比示例:

// 委托
public Action MyDelegate;
MyDelegate = () => Console.WriteLine("Delegate called"); // 外部可随意覆盖
MyDelegate(); // 外部可触发

// 事件
public event Action MyEvent;
MyEvent = () => Console.WriteLine("Error!"); // 编译错误(外部不可赋值)
MyEvent?.Invoke(); // 编译错误(外部不可触发)

第二部分:匿名函数 

一、什么是匿名函数

        所谓匿名函数,就是没有名字的函数,那他有啥用呢。他主要是和委托和事件一起玩儿,可以说离开了这两家伙,匿名函数根本就没任何用处。

        匿名函数是 C# 中一种简化委托和事件使用的语法糖,它允许开发者直接内联定义函数逻辑,而无需显式声明方法名

匿名函数:没有名字的函数
匿名函数的作用主要是配合着委托和事件使用
脱离委托和事件,匿名函数没有意义

 二、匿名函数的基本申明规则以及使用示例

申明规则:

delegate(参数列表)
{
    函数体
};
何时使用?
1.函数中传递委托函数时
2.委托或事件赋值时

示例 1:匿名函数赋值给委托变量

using System;

// 定义委托类型
public delegate int MathOperation(int a, int b);

public class Program
{
    public static void Main()
    {
        // 匿名函数实现加法
        MathOperation add = delegate(int x, int y) 
        { 
            return x + y; 
        };

        Console.WriteLine(add(3, 5)); // 输出 8
    }
}

 示例 2:匿名函数订阅事件

using System;

public class Button
{
    public event Action Clicked;

    public void Press()
    {
        Clicked?.Invoke();
    }
}

public class Program
{
    public static void Main()
    {
        Button button = new Button();

        // 使用匿名函数订阅事件
        button.Clicked += delegate
        {
            Console.WriteLine("按钮被点击了!");
        };

        button.Press(); // 输出 "按钮被点击了!"
    }
}

示例 3:匿名函数访问外部变量(闭包)这个闭包我们在下面的第三部分学习,这里先看事件的使用

using System;

public class Program
{
    public static void Main()
    {
        int counter = 0;

        Action increment = delegate
        {
            counter++;  // 访问外部变量 counter
            Console.WriteLine($"当前值:{counter}");
        };

        increment(); // 输出 "当前值:1"
        increment(); // 输出 "当前值:2"
    }
}

三、匿名函数的优缺点

优点
  1. 简化代码:无需单独定义方法,减少代码量。

  2. 灵活性强:可直接访问外层变量(闭包),适合快速实现临时逻辑。

  3. 减少类成员:避免因简单逻辑污染类的成员列表。

缺点
  1. 可读性差:复杂逻辑内联在匿名函数中会降低代码可读性。

  2. 难以重用:匿名函数无法被其他代码直接调用。

  3. 闭包陷阱:若匿名函数引用外部变量,可能导致变量生命周期延长(内存泄漏风险)。

  4. 调试困难:匿名函数在堆栈跟踪中显示为不可见的方法名(如 <Main>b__0)。

  5. 添加到委托或者事件容器中 不记录 无法单独移除

第三部分:Lambda表达式

一、什么是 Lambda 表达式?

        Lambda 表达式是 C# 中一种更简洁的匿名函数写法,本质上仍是匿名函数,但语法更精简。它通过 => 符号(读作“goes to”)连接参数列表和方法体,核心目的是简化委托和事件的代码

可以将Lambda表达式理解为一种匿名函数的简写
他除了写法不同以外
使用上和匿名函数一模一样
都是和委托或者事件 配合使用的

关键特性:
  1. 匿名性:无需显式定义方法名。

  2. 类型推断:参数类型可省略(由编译器自动推断)。

  3. 灵活性:支持表达式体(单行代码)和语句体(多行代码)。

二、Lambda 表达式的语法

Lambda 表达式的基本语法如下:

        Lambda表达式
        (参数列表) => { 函数体 }

  • 参数列表

    • 无参数:() => ...

    • 单参数:x => ...(可省略括号)

    • 多参数:(x, y) => ...

  • 表达式体:单行代码,自动返回结果(无需 return)。

  • 语句体:多行代码,需用 { } 包裹,且需显式使用 return

三、Lambda 表达式的使用示例
1. 有参有返回(显式类型)
// 显式声明参数类型
Func<int, int, int> add = (int x, int y) => x + y;
Console.WriteLine(add(3, 5)); // 输出 8
2. 有参有返回(显式类型,语句体)
// 多行代码需用 { } 和 return
Func<int, int, int> multiply = (int a, int b) => 
{
    int result = a * b;
    return result;
};
Console.WriteLine(multiply(4, 5)); // 输出 20
3. 无参有返回
// 无参数时必须保留 ()
Func<int> getRandom = () => new Random().Next(1, 100);
Console.WriteLine(getRandom()); // 输出随机数
4. 无参无返回
// Action 表示无返回值
Action logMessage = () => Console.WriteLine("Hello, Lambda!");
logMessage(); // 输出 "Hello, Lambda!"
5. 不显式声明类型(类型推断) 
// 参数类型由编译器推断
Func<int, int, int> subtract = (x, y) => x - y;
Console.WriteLine(subtract(10, 3)); // 输出 7

// 单参数可省略括号
Action<string> greet = name => Console.WriteLine($"你好,{name}!");
greet("张三"); // 输出 "你好,张三!"
四、什么是闭包

        闭包是函数式编程中的一个核心概念,在 C# 中通过 Lambda 表达式匿名函数实现。它的本质是:
        一个函数(Lambda/匿名函数)可以捕获并访问其外部作用域中的变量,即使外部作用域已经退出
        闭包的核心特性是延长变量的生命周期,使得外部变量不会被垃圾回收(GC),直到闭包本身不再被引用。

        简单地说就是改变了变量的生命周期,例如本来在一个函数里面的变量,结果在类中还可以修改,这就是闭包。

内层的函数可以引用包含在它外层的函数的变量
即使外层的函数的执行已经终止
注意;
该变量提供的值并非变量创建时的值,而是在父函数范围内的最终值

1. 闭包的基本原理
  • 捕获外部变量:Lambda 表达式或匿名函数可以“记住”定义时所在作用域的变量。

  • 变量的生命周期:被捕获的变量会一直存活,直到闭包不再被使用。

示例 1:简单闭包

using System;

Func<int> CreateCounter()
{
    int count = 0; // 外部变量
    // 闭包捕获 count
    return () => ++count; // Lambda 表达式
}

public static void Main()
{
    var counter = CreateCounter();
    Console.WriteLine(counter()); // 输出 1
    Console.WriteLine(counter()); // 输出 2
    Console.WriteLine(counter()); // 输出 3
}

解释:

关键点

  • CreateCounter 方法执行完毕后,局部变量 count 本应被销毁,但由于闭包的存在,它的生命周期被延长。

  • 每次调用 counter() 时,闭包操作的 count 是同一个变量。

2. 闭包捕获的是变量,不是值

        闭包捕获的是变量的引用,而不是变量在某一时刻的值。这意味着如果外部变量后续被修改,闭包中看到的是修改后的值。

示例 2:闭包与循环陷阱
var actions = new List<Action>();
for (int i = 0; i < 3; i++)
{
    actions.Add(() => Console.WriteLine(i));
}

foreach (var action in actions)
{
    action(); // 输出 3, 3, 3(而非 0, 1, 2)
}

原因

  • 闭包捕获的是循环变量 i 的引用,而不是每次循环时的值。

  • 循环结束时,i 的值为 3,所有闭包共享同一个 i

3. 如何避免循环陷阱?

通过创建局部变量的副本,让闭包捕获独立的变量:

修复示例:
var actions = new List<Action>();
for (int i = 0; i < 3; i++)
{
    int temp = i; // 每次循环创建一个新变量 temp
    actions.Add(() => Console.WriteLine(temp)); // 捕获 temp
}

foreach (var action in actions)
{
    action(); // 输出 0, 1, 2
}

每次循环都会创建一个新的 temp 变量,闭包捕获的是不同的 temp

4. 闭包的常见应用场景
  1. 事件处理:在事件回调中访问外部变量。

  2. 异步编程:在 async/await 中捕获上下文变量。

  3. 延迟执行:将逻辑封装为闭包,延迟到特定时机执行。

  4. 工厂模式:生成具有独立状态的函数(如示例 1 的计数器)。

5.闭包的注意事项

 1.避免循环引用:闭包引用外部对象可能导致内存泄漏。

 2.谨慎使用闭包捕获可变变量:共享变量可能导致线程安全问题。

 小结:

  1. 闭包的本质:Lambda/匿名函数捕获外部作用域的变量,延长其生命周期。

  2. 核心价值:简化代码,支持函数式编程范式。

  3. 核心风险:内存泄漏和逻辑陷阱(如循环变量共享)。

第四部分:委托,事件,匿名函数的总结 

特性委托(Delegate)事件(Event)匿名函数(Lambda/匿名方法)
本质类型安全的函数指针,用于封装方法对委托的封装,提供更安全的访问控制无名称的内联函数,依赖委托或事件存在
主要作用定义方法签名,实现回调机制实现发布-订阅模型,解耦对象通信简化委托/事件的代码,处理临时逻辑
访问权限公共成员,外部可直接调用或赋值外部只能通过 += 和 -= 订阅或取消订阅仅能通过委托或事件间接使用
多播支持支持(可链式调用多个方法)支持(本质是多播委托)依赖委托的多播能力
典型应用场景回调方法、LINQ、异步编程GUI 交互(如按钮点击)、消息通知系统事件处理、简单逻辑封装(如排序规则)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

FAREWELL00075

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值