c#委托、lambda、事件

Lambda

Lambda表达式是一种匿名函数 ,Lambda表达式通常以箭头****"=>****"分隔左侧的输入和右侧的输出。

(parameter_list) => { statement_block }

parameter_list

是由一个或多个参数组成的逗号分隔列表,每个参数都包括类型和名称,可以为空。

如果只有一个参数,只写参数名即可,不用圆括号。

Func<int, int> add = y => 1 + y;

如果是多个参数,把参数放在圆括号内。

Func<int, int,int> add = (x,y) => x + y;

statement_block

是Lambda表达式的主体。statement_block表示一段代码块,它可以包含多个语句,多个语句使用大括号包裹。

如果Lambda表达式只有一句语句,statement_block不需要花括号和return语句。

Func<int, int,int> add = (x,y) => x + y;

如果是含有多条语句,必须加上花括号和return语句。

Func<int, int,int> add = (x,y) =>

{

X = x+1;

Y= y+1;

Return x+y;

}

常用的方法

  1. Where

Where方法是IEnumerable<T>接口的扩展方法,它筛选序列中满足指定条件的元素,返回满足条件的元素序列.

List<int> numbers = new List<int> { 1, 2, 3, 4, 5 };

var oddNumbers = numbers.Where(x => x % 2 != 0);

  1. Select

它将序列中的每个元素投影到新的形式,返回投影后的元素序列.

List<int> numbers = new List<int> { 1, 2, 3, 4, 5 };

var squaredNumbers = numbers.Select(x => x * x);

  1. Orderby

按照指定的键对序列中的元素进行排序.

List<string> words = new List<string> { "apple", "banana", "cherry", "date" };

var sortedWords = words.OrderBy(x => x.Length);

  1. GroupBy

根据指定的键对序列中的元素进行分组.

List<string> words = new List<string> { "apple", "banana", "cherry", "date" };

var groupedWords = words.GroupBy(x => x[0]);

Lambda表达式中的闭包

闭包是指一个函数能够访问并操作在它的外部作用域中定义的变量。在Lambda表达式中,可以使用闭包来访问外部作用域中的变量.

int x = 5;

Func<int, int> add = y => x + y;

Console.WriteLine(add(3));

委托
  1. 委托是一种引用类型,表示对具有特定参数列表和返回类型的方法的引用。
  2. 在实例化委托时,你可以将其实例与任何具有兼容签名和返回类型的方法相关联。 委托可以链接在一起,一次性调用多个方法
  3. 你可以通过委托实例调用方法。
  4. 委托用于将方法作为参数传递给其他方法,可用于定义回调方法
  5. 可将任何可访问类或结构中与委托类型匹配的任何方法分配给委托。该方法可以是静态方法,也可以是实例方法。 此灵活性意味着你可以通过编程方式来更改方法调用,还可以向现有类中插入新代码。
  6. 委托类型派生自 .NET 中的 Delegate 类。 委托类型是密封的,它们不能派生自 Delegate,也不能从其派生出自定义类

简单地说:委托类似c++的函数指针。

如何使用委托

委托是一种引用类型,虽然在定义委托时与方法有些相似,但不能将其称为方法。

委托在使用时遵循三步走的原则,即定义声明委托、实例化委托以及调用委托

public class ShowTest

{

public delegate void ShowDelegate(string strText);

public Action<string> action;

public void Show(string strText)

{

Console.WriteLine(strText);

}

public void test()

{

ShowDelegate showDelegate1 = new ShowDelegate(Show);

ShowDelegate showDelegate2 = Show;

ShowDelegate showDelegate3 = delegate (string strText)

{

Console.WriteLine(strText);

};

ShowDelegate showDelegate4 = (string strText) => { Console.WriteLine(strText); };

string Text = "hello";

showDelegate1(Text);

showDelegate2.Invoke(Text);

action = Show;

action(Text);

}

}

声明委托

修饰符 delegate 返回值类型 委托名 ( 参数列表 );

如:public delegate void ShowDelegate ();

在命名空间内,但不在类内

实例化委托

方式1:通过new 创建委托实例

必须传入一个方法作为参数,否则会报错因为委托内部的构造函数,需求传递一个方法作为参数。

委托名 委托对象名 = new 委托名 ( 方法名 );

委托中传递的方法名既可以是静态方法 的名称,也可以是实例方法的名称。需要注意的是,在委托中所写的方法名必须与委托定义时的返回值类型和参数列表相同。

ShowDelegate showDelegate1 = new ShowDelegate(Show);

方式2:使用赋值的方式

ShowDelegate showDelegate2 = Show;

方式3:匿名委托

ShowDelegate showDelegate3 = delegate (string strText)

{

Console.WriteLine(strText);

};

方式4:Lambda

ShowDelegate showDelegate4 = (string strText) => { Console.WriteLine(strText); };

调用委托

方式1:直接调用委托的变量

showDelegate1(Text);

方式2:invoke()

showDelegate2.Invoke(Text);

匿名委托

匿名方法的意义在于:快速方便的实例化委托,不用定义具体的方法来关联委托,就是临时定义个方法(处理逻辑)与委托相关联。只是在实例化委托按照下面的格式处理。

委托类型 变量名 = delegate( 形参 ) { 逻辑处理语句 };

Func<string, string> dele = delegate (string strpa)

{

string strq = "sa";

return strq;

};

dele(str);

缺点:不能在其他地方被调用,即不具有复用性。 而且,匿名方法会将自动形成闭包。当一个函数(这里称为外部函数)包含对另一个函数(内部函数)的调用时,或内部函数使用了外部函数的变量时都会形成闭包。

闭包

在C#中我们可以使用Lambda来实现闭包,闭包本质是一个对象(编译后),但使用上它和方法一致。使用闭包我们就可以实现拥有私有状态的函数!

定义:我们把在Lambda表达式(或匿名方法)中所引用的外部变量称为捕获变量。而捕获变量的表达式就称为闭包。

捕获变量:捕获的变量会在真正调用委托时"赋值",而不是在捕获时"赋值",即总是使用捕获变量的最新的值。

作用:内层的函数可以引用包含在它外层的函数的变量,即使外层函数的执行已经终止。

public class ShowTest

{

public Action<string> action1;

ShowTest()

{

string tt = "qq";

action1 = (string str) => { Console.WriteLine(tt + str); };

}

public void test()

{

string Text = "hello";

action1(Text);

}

}

泛型委托

由于单独定义委托和事件,比较繁琐,而且比较冗余,因此C#2.0提供了ActionFunc两个泛型委托,不用单独申明,拿来就可以用。

Action<T>

(1)Action 表示无参,无返回值的委托

(2)Action<int,string> 表示有参,无返回值的泛型委托,最多可入参16个

(3)使用Action 就可以囊括所有无返回值委托,可以说Action事对无返回值委托的进一步包装

public class ShowTest

{

public Action<string> action;

public void Show(string strText)

{

Console.WriteLine(strText);

}

public void test()

{

action = Show;

//或者 通过new 创建委托实例 委托名 委托对象名 = new 委托名 ( 方法名 );

action = new Action<string>(Show);

action(Text);

}

}

Func<T>

(1)Func 表示有返回值的委托(必须有返回值)

(2)Func可以无参数,也可以有参数,最多16个参数,最后一个表示返回值且只有一个

(3)使用方法同delegate,Func不过是对所有的有返回值的数据进行了一个包装

public class ShowTest

{

public Func<int, int,int> addfunc;

public int add(int a,int b)

{

return a + b;

}

public void test()

{

addfunc = add;

int nRet = addfunc(1, 2);

}

}

多播委托

通过****+=**** 绑定多个方法到这个委托,从而形成委托链。执行委托的时候,按照添加方法的顺序,依次去执行方法

注意:

1.action.BeginInvoke();会开启一个新的线程去执行委托,注册有多个方法的委托,不能使用BeginInvoke。

2.注册有多个方法的委托想要开启新线程去执行委托,可以通过action.GetInvocationList()获取到所有的委托,然后循环,每个方法执行的时候可以BeginInvoke

3.使用多播委托的时候可能会遇到一个问题,就是委托链的第一个方法报错了,导致后面的注册的方法都无法调用。解决办法:使用GetInvocationList 按照调用顺序返回此多播委托的调用列表。

public class ShowTest

{

public delegate void BuySomethingDelegate();

public void BuyWater()

{

Console.WriteLine("买水!");

}

public void BuyKFC()

{

Console.WriteLine("买肯德基");

}

public void BuyHotDog()

{

Console.WriteLine("买热狗");

}

public void test()

{

BuySomethingDelegate bsd = new BuySomethingDelegate(BuyWater);

bsd += BuyHotDog;

bsd += BuyKFC;

bsd -= BuyHotDog;

Delegate[] delegateArr = bsd.GetInvocationList();

foreach (BuySomethingDelegate item in delegateArr)

{

try

{

item.Invoke();

}

catch (Exception)

{

Console.WriteLine($"{item.Method.Name}方法报错了!");

}

}

}

}

延伸:

Invoke

Invoke的本质只是一个方法,方法一定是要通过对象来调用的。

Control的Invoke、Delegate的Invoke

也就是说Invoke前面要么是一个控件,要么是一个委托对象。

Control的Invoke

public object Invoke(Delegate method);

public object Invoke(Delegate method, params object[] args);

Control的Invoke一般用于解决跨线程 访问的问题。即在子线程中让主线程的控件调用Invoke函数,从而操作主线程的UI空间。

使用Invoke完成一个委托方法的封送,就类似于使用SendMessage 阻塞

Delegate的Invoke

目的作用就是执行委托。 Delegate的Invoke其实就是从线程池中调用委托方法执行Invoke是同步的方式,会卡住调用它的UI线程。

BeginInvoke

使用BeginInvoke方法封送一个委托方法,类似于使用PostMessage 进行通信,这是一个异步方法。

Control的BeginInvoke

Control类上的异步调用BeginInvoke并没有开辟新的线程完成委托任务,而是让界面控件的所属线程完成委托任务的。(还是在同一线程执行)

Delegate的BeginInvoke

Delegate.BeginInvoke方法是从ThreadPool中取出的一个线程来执行这个方法,以获得异步的执行效果。(不同的线程执行)

事件

事件本质上来讲是一种特殊的多播委托

作用:C# 中常常会使用事件 来实现线程之间的通信。

在 C# 中,类或对象可以通过事件向其他类或对象通知发生的相关事情。这种模式通常称为发布订阅模型,发送(或引发)事件的类称为"发布者",接收(或处理)事件的类称为"订阅者"。所以事件主要用于发布订阅者模式(即设计模式中的观察者模式)。

好处:就是解耦

发布者 : 一个创建了事件和委托定义的对象,同时也包含了事件和委托之间的联系与具体行为。发布者的任务就是执行这些事件,并通知程序中的其它对象。

订阅者 : 一个接收事件并提供事件处理程序的对象。订阅者中的方法(事件处理程序)用于分配给发布者中的委托。

简单的来说,发布者确定何时引发事件,而订阅者确定对事件作出何种响应。

using System;

事件的声明

事件的本质还是委托,所以要声明一个事件之前必须先声明一个相对应的委托。

public delegate void PubDelegate();

在 C# 中,事件需要使用关键字 event

<Access Specifier > event <Delegate> <Event Name>

public event PubDelegate PubEvent;

事件的使用范围:

仅可以从声明事件的类(或派生类)或结构(发布服务器类)中对其进行调用。

// 发布者类

public class PublisherClass

{

// 和事件搭配的委托

public delegate void PubDelegate();

// 定义事件

public event PubDelegate PubEvent;

// 编写处理事件的具体逻辑

public void EventHandling()

{

if (PubEvent == null)

{

Console.WriteLine("需要注册事件的啊");

}

else

{

// 执行注册的事件

PubEvent();

}

}

}

// 订阅者类

public class SubscriberClass

{

public void printout()

{

Console.WriteLine("执行了订阅者类中的事件。");

Console.ReadLine();

}

}

public class Program

{

static void Main()

{

// 实例化对象

PublisherClass p = new PublisherClass();

SubscriberClass s = new SubscriberClass();

// 执行事件

p.EventHandling();

// 注册事件

p.PubEvent += new PublisherClass.PubDelegate(s.printout);

// 执行事件

p.EventHandling();

}

}

事件委托之EventHandler

想直接用事件且懒得去声明委托,这时候微软给我提供了一个委托叫EventHandler,主要是给事件服务的。

事件的声明就是:

public event EventHandler 事件名;

public delegate void EventHandler(object sender, EventArgs e);

object sender:一般写this

EventArgs e:事件参数,如果无参数就写成EventArgs.Empty

using System;

namespace CSharplearn

{

class Program

{

static void Main(string[] args)

{

Trigger trigger = new Trigger();

trigger.TrigMethod();

}

}

class Trigger

{

public event EventHandler TrigEvent;

public void TrigMethod()

{

TrigEvent += Logic.Printer;

TrigEvent(this,EventArgs.Empty);

}

}

class Logic

{

public static void Printer(object sender,EventArgs e)

{

Console.WriteLine("Hello World");

}

}

}

泛型兄弟长这样:

public delegate void EventHandler<TEventArgs>(object sender, TEventArgs e);

using System;

namespace CSharplearn

{

class Program

{

static void Main(string[] args)

{

Trigger trigger = new Trigger();

trigger.TrigMethod();

}

}

class Trigger

{

public event EventHandler<MyEventArgs> TrigEvent;

public void TrigMethod()

{

MyEventArgs myArgs = new MyEventArgs();

myArgs.String = "Hello Space";

TrigEvent += Logic.Printer;

TrigEvent(this, myArgs);

//或者是 TrigEvent?.Invoke(this, myArgs);

}

}

class Logic

{

public static void Printer(object sender,MyEventArgs e)

{

Console.WriteLine(e.String);

}

}

public class MyEventArgs : EventArgs

{

public string String;

}

}

事件与委托的异同:

相同点:

事件其实是一个多播委托,本质上是一样的。

不同点:

可调用位置不同:事件只能在声明事件的类中才能调用,而委托无论是在类的内部还是外部都可以调用。

可使用符号不同:事件只能使用 += 和 -= 符号来订阅和取消订阅,但是委托不仅可以使用 += 和 -= 符号还可以使用 = 符号进行方法分配。

相关推荐
shanshan209913 分钟前
上位机系统架构 | 如何设计一个高效的多相机管理系统
c#·wpf·相机
ling1s2 小时前
C#基础(13)结构体
前端·c#
.Net Core 爱好者2 小时前
Redis实践之缓存:设置缓存过期策略
java·redis·缓存·c#·.net
云草桑2 小时前
逆向工程 反编译 C# net core
前端·c#·反编译·逆向工程
指尖流烟4 小时前
C#调用图表的使用方法
开发语言·c#
friklogff6 小时前
【无标题】云端之C#:全面解析6种云服务提供商的SDK
开发语言·flask·c#
c#上位机7 小时前
C#事件的用法
java·javascript·c#
chnyi6_ya7 小时前
一些写leetcode的笔记
笔记·leetcode·c#
IT规划师7 小时前
C#|.net core 基础 - 扩展数组添加删除性能最好的方法
c#·.netcore·数组
时光追逐者8 小时前
分享6个.NET开源的AI和LLM相关项目框架
人工智能·microsoft·ai·c#·.net·.netcore