C# 委托(Delegate)与事件(Event)
C# 委托是.NET Framework 使用的一种类型安全的函数指针。委托通常用于实现回调和事件侦听器。委托是引用类型。但是委托不是引用对象,而是引用方法。.NET 框架内置的委托事件(Event)基本上说是一个用户操作,如按键、点击、鼠标移动等等,或者是一些提示信息,如系统生成的通知。应用程序需要在事件发生时响应事件。
一、委托 Delegate
1. 什么是委托?
C# 委托是.NET Framework 使用的一种类型安全的函数指针。
委托通常用于实现回调和事件侦听器。
委托是引用类型。 但是委托不是引用对象,而是引用方法。
2. 委托的作用?
1. 将函数作为函数的的参数进行传递
2. 声明事件并用来注册
3. 委托的使用
1.委托基本示例
// 1.声明一个无参无返回值的委托
delegate void MyDelegate();
class Program
{
static void Main(string[] args)
{
// 2. 创建委托的示例
var md = new MyDelegate(MyCallback);
// 3. 调用时,委托将调用静态的MyCallback方法
md();
}
// 被委托的方法,这个方法的签名要和委托 一致
static void MyCallback()
{
Console.WriteLine("Calling callback");
}
}
声明一个无参无返回值的委托
delegate void MyDelegate();
调用的方法需要和声明的委托的签名一致
var md = new MyDelegate(MyCallback);
我们创建委托的实例。 调用时,委托将调用静态MyCallback()
方法。
md();
输出:
$ dotnet run
Calling callback
4. 委托指向不同的方法
1. 示例:在此示例中, 该委托用于指向Person
类的两个方法。 方法与委托一起调用。
namespace DifferentMethods
{
public delegate void NameDelegate(string msg);
public class Person
{
public string firstName;
public string secondName;
public Person(string firstName, string secondName)
{
this.firstName = firstName;
this.secondName = secondName;
}
public void ShowFirstName(string msg)
{
Console.WriteLine(msg + this.firstName);
}
public void ShowSecondName(string msg)
{
Console.WriteLine(msg + this.secondName);
}
}
class Program
{
public static void Main()
{
var per = new Person("Fabius", "Maximus");
var nDelegate = new NameDelegate(per.ShowFirstName);
nDelegate("Call 1: ");
nDelegate = new NameDelegate(per.ShowSecondName);
nDelegate("Call 2: ");
}
}
}
2. 使用delegate
关键字创建委托。 委托签名必须与委托调用的方法的签名一致。
public delegate void NameDelegate(string msg);
3. 创建一个新委托的实例,该实例指向ShowFirstName()
方法。 通过委托调用该方法。
var nDelegate = new NameDelegate(per.ShowFirstName);
nDelegate("Call 1: ");
4. 输出:这两个输出结果都是通过代理打印的
$ dotnet run
Call 1: Fabius
Call 2: Maximus
5. 多播委托
1. 多播委托定义:多播委托是一个拥有对多个方法的引用的委托。 多播委托必须仅包含返回 void 的方法,否则将存在运行时异常。
2. 多播委托示例
using System;
namespace MulticastDelegate
{
delegate void MyDelegate(int x, int y);
public class Oper
{
public static void Add(int x, int y)
{
Console.WriteLine("{0} + {1} = {2}", x, y, x + y);
}
public static void Sub(int x, int y)
{
Console.WriteLine("{0} - {1} = {2}", x, y, x - y);
}
}
class Program
{
static void Main()
{
var del = new MyDelegate(Oper.Add);
del += new MyDelegate(Oper.Sub);
del(6, 4);
del -= new MyDelegate(Oper.Sub);
del(2, 8);
}
}
}
3. 声明一个委托,无返回值和接收两个参数的委托, 这里有一个Oper
类,它具有两个静态方法。
delegate void MyDelegate(int x, int y);
4. 创建委托的实例,委托只想Oper类的静态Add()方法。
var del = new MyDelegate(Oper.Add);
5.将另一个方法插入到现有的委托实例中。 委托的第一次调用将调用两个方法
del += new MyDelegate(Oper.Sub);
del(6, 4);
6.从委托中删除一种方法。 委托的第二次调用仅调用一种方法。
del -= new MyDelegate(Oper.Sub);
del(2, 8);
7.输出
$ dotnet run
6 + 4 = 10
6 - 4 = 2
2 + 8 = 10
6.匿名方法
1. 示例:当将匿名方法与委托一起使用时,我们可以省略方法声明。 该方法没有名称,只能通过委托来调用。
Program.
using System;
namespace Anonymous
{
delegate void MyDelegate();
class Program
{
static void Main(string[] args)
{
MyDelegate del = delegate
{
Console.WriteLine("Anonymous method");
};
del();
}
}
}
2. 在这里,创建一个指向匿名方法的委托。 匿名方法的主体用{}
字符括起来,但是没有名称。
MyDelegate del = delegate
{
Console.WriteLine("Anonymous method");
};
7.C# 委托作为方法参数
委托可以用作方法参数。
示例:有一个DoOperation()
方法,该方法将一个委托作为参数,把需要代理的方法传到DoOperation()
方法进行调用
using System;
namespace MethodParameters
{
delegate int Arithm(int x, int y);
class Program
{
static void Main()
{
DoOperation(10, 2, Multiply);
DoOperation(10, 2, Divide);
}
static void DoOperation(int x, int y, Arithm del)
{
int z = del(x, y);
Console.WriteLine(z);
}
static int Multiply(int x, int y)
{
return x * y;
}
static int Divide(int x, int y)
{
return x / y;
}
}
}
这是一个委托声明。
delegate int Arithm(int x, int y);
这是DoOperation()
方法的实现。 第三个参数是委托。 DoOperation()
方法调用一个方法,该方法作为第三个参数传递给它。
static void DoOperation(int x, int y, Arithm del)
{
int z = del(x, y);
Console.WriteLine(z);
}
我们称为DoOperation()
方法。 我们传递两个值和一个方法给它。 我们对这两个值的处理方式取决于我们通过的方法。 这就是使用委托所带来的灵活性。
DoOperation(10, 2, Multiply);
DoOperation(10, 2, Divide);
输出
$ dotnet run
20
5
二、预定义的委托
.NET 框架内置的委托
1. Action委托
Action 是.NET Framework内置的泛型委托,可以使用Action 委托以参数形式传递方法,而不用显示声明自定义的委托。封装的方法必须与此委托定义的方法签名相对应。也就是说,封装的方法必须具有一个通过值传递给它的参数,并且不能有返回值。
Action定义:Action其实就是没有返回值的delegate。
using System.Runtime.CompilerServices;
namespace System
{
// 摘要:
// 封装一个方法,该方法不具有参数且不返回值。
[TypeForwardedFrom("System.Core, Version=3.5.0.0, Culture=Neutral, PublicKeyToken=b77a5c561934e089")]
public delegate void Action();
}
Action委托至少0个参数,至多16个参数,无返回值。
Action 表示无参,无返回值的委托。
Action<int,string> 表示有传入参数int,string无返回值的委托。
Action<int,string,bool> 表示有传入参数int,string,bool无返回值的委托。
Action<int,int,int,int> 表示有传入4个int型参数,无返回值的委托。
class Program
{
static void Main(string[] args)
{
// 无参数无返回值的委托
Action action1 = new Action(ActionWithNoParaNoReturn);
action1();
Console.WriteLine("----------------------------");
// 使用delegate
Action action2 = delegate { Console.WriteLine("这里是使用delegate"); };
// 执行
action2();
Console.WriteLine("----------------------------");
// 使用匿名委托
Action action3 = () => { Console.WriteLine("这里是匿名委托"); };
action3();
Console.WriteLine("----------------------------");
// 有参数无返回值的委托
Action<int> action4 = new Action<int>(ActionWithPara);
action4(23);
Console.WriteLine("----------------------------");
// 使用delegate
Action<int> action5 = delegate (int i) { Console.WriteLine($"这里是使用delegate的委托,参数值是:{i}"); };
action5(45);
Console.WriteLine("----------------------------");
// 使用匿名委托
Action<string> action6 = (string s) => { Console.WriteLine($"这里是使用匿名委托,参数值是:{s}"); };
action6("345");
Console.WriteLine("----------------------------");
// 多个参数无返回值的委托
Action<int, string> action7 = new Action<int, string>(ActionWithMulitPara);
action7(7, "abc");
Console.WriteLine("----------------------------");
// 使用delegate
Action<int, int, string> action8 = delegate (int i1, int i2, string s)
{
Console.WriteLine($"这里是三个参数的Action委托,参数1的值是:{i1},参数2的值是:{i2},参数3的值是:{s}");
};
action8(12, 34, "abc");
Console.WriteLine("----------------------------");
Action<int,int,string, string> action9 = (int i1,int i2, string s1,string s2) =>
{
Console.WriteLine($"这里是使用四个参数的委托,参数1的值是:{i1},参数2的值是:{i2},参数3的值是:{s1},参数4的值是:{s2}");
};
// 执行委托
action9(34,56, "abc","def");
Console.ReadKey();
}
static void ActionWithNoParaNoReturn()
{
Console.WriteLine("这是无参数无返回值的Action委托");
}
static void ActionWithPara(int i)
{
Console.WriteLine($"这里是有参数无返回值的委托,参数值是:{i}");
}
static void ActionWithMulitPara(int i,string s)
{
Console.WriteLine($"这里是有两个参数无返回值的委托,参数1的值是:{i},参数2的值是:{s}");
}
}
2. Func委托
Func委托代表有返回类型的委托,Func其实就是有多个输出参数并且有返回值的delegate
Func的定义
using System.Runtime.CompilerServices;
namespace System
{
//
// 摘要:
// 封装一个方法,该方法具有两个参数,并返回由 TResult 参数指定的类型的值。
//
// 参数:
// arg1:
// 此委托封装的方法的第一个参数。
//
// arg2:
// 此委托封装的方法的第二个参数。
//
// 类型参数:
// T1:
// 此委托封装的方法的第一个参数的类型。
//
// T2:
// 此委托封装的方法的第二个参数的类型。
//
// TResult:
// 此委托封装的方法的返回值类型。
//
// 返回结果:
// 此委托封装的方法的返回值。
[TypeForwardedFrom("System.Core, Version=3.5.0.0, Culture=Neutral, PublicKeyToken=b77a5c561934e089")]
public delegate TResult Func<in T1, in T2, out TResult>(T1 arg1, T2 arg2);
}
Func至少0个输入参数,至多16个输入参数,根据返回值泛型返回。必须有返回值,不可void。
Func 表示没有输入参参,返回值为int类型的委托。
Func<object,string,int> 表示传入参数为object, string ,返回值为int类型的委托。
Func<object,string,int> 表示传入参数为object, string, 返回值为int类型的委托。
Func<T1,T2,,T3,int> 表示传入参数为T1,T2,,T3(泛型),返回值为int类型的委托。
实例
class Program
{
static void Main(string[] args)
{
// 无参数,只要返回值
Func<int> fun1 = new Func<int>(FunWithNoPara);
int result1= fun1();
Console.WriteLine(result1);
Console.WriteLine("----------------------------");
Func<int> fun2 = delegate { return 19; };
int result2 = fun2();
Console.WriteLine(result2);
Console.WriteLine("----------------------------");
Func<int> fun3 = () => { return 3; };
int result3 = fun3();
Console.WriteLine(result3);
Console.WriteLine("----------------------------");
//有一个参数,一个返回值
Func<int, int> fun4 = new Func<int, int>(FunWithPara);
int result4 = fun4(4);
Console.WriteLine($"这里是一个参数一个返回值的方法,返回值是:{result4}");
Console.WriteLine("----------------------------");
// 使用委托
Func<int, string> fun5 = delegate (int i) { return i.ToString(); };
string result5 = fun5(5);
Console.WriteLine($"这里是一个参数一个返回值的委托,返回值是:{result5}");
Console.WriteLine("----------------------------");
// 使用匿名委托
Func<int, string> fun6 = (int i) =>
{
return i.ToString();
};
string result6 = fun6(6);
Console.WriteLine($"这里是一个参数一个返回值的匿名委托,返回值是:{result6}");
Console.WriteLine("----------------------------");
// 多个输入参数
Func<int, string, bool> fun7 = new Func<int, string, bool>(FunWithMultiPara);
bool result7 = fun7(2, "2");
Console.WriteLine($"这里是有多个输入参数的方法,返回值是:{result7}");
Console.WriteLine("----------------------------");
// 使用委托
Func<int, string, bool> fun8 = delegate (int i, string s)
{
return i.ToString().Equals(s) ? true : false;
};
bool result8 = fun8(2, "abc");
Console.WriteLine($"这里是有多个输入参数的委托,返回值是:{result8}");
Console.WriteLine("----------------------------");
// 使用匿名委托
Func<int, string, bool> fun9 = (int i, string s) =>
{
return i.ToString().Equals(s) ? true : false;
};
bool result9 = fun9(45, "ert");
Console.WriteLine($"这里是有多个输入参数的匿名委托,返回值是:{result9}");
Console.ReadKey();
}
static int FunWithNoPara()
{
return 10;
}
static int FunWithPara(int i)
{
return i;
}
static bool FunWithMultiPara(int i,string s)
{
return i.ToString().Equals(s) ? true : false;
}
}
三、事件 Event
1.定义
事件(Event) 基本上说是一个用户操作,如按键、点击、鼠标移动等等,或者是一些提示信息,如系统生成的通知。应用程序需要在事件发生时响应事件
2.作用
C# 事件(Event)是一种成员,用于将特定的事件通知发送给订阅者。事件通常用于实现观察者模式,它允许一个对象将状态的变化通知其他对象,而不需要知道这些对象的细节。
希望一个类的某些成员在发生变化时能够被外界观测到
关键点:
- 声明委托:定义事件将使用的委托类型。委托是一个函数签名。
- 声明事件:使用
event
关键字声明一个事件。 - 触发事件:在适当的时候调用事件,通知所有订阅者。
- 订阅和取消订阅事件:其他类可以通过
+=
和-=
运算符订阅和取消订阅事件。
3. 通过事件使用委托
事件在类中声明且生成,且通过使用同一个类或其他类中的委托与事件处理程序关联。包含事件的类用于发布事件。这被称为 发布器(publisher) 类。其他接受该事件的类被称为 订阅器(subscriber) 类。事件使用 发布-订阅(publisher-subscriber) 模型。
发布器(publisher) 是一个包含事件和委托定义的对象。事件和委托之间的联系也定义在这个对象中。发布器(publisher)类的对象调用这个事件,并通知其他的对象。
订阅器(subscriber) 是一个接受事件并提供事件处理程序的对象。在发布器(publisher)类中的委托调用订阅器(subscriber)类中的方法(事件处理程序)。
4.声明事件(Event)
在类的内部声明事件,首先必须声明该事件的委托类型。例如:
public delegate void BoilerLogHandler(string status);
然后,声明事件本身,使用 event 关键字:
// 基于上面的委托定义事件
public event BoilerLogHandler BoilerEventLog;
上面的代码定义了一个名为 BoilerLogHandler 的委托和一个名为 BoilerEventLog 的事件,该事件在生成的时候会调用委托。
5.示例
以下示例展示了如何在 C# 中使用事件:
using System;
using System.Diagnostics;
namespace DifferentMethods
{
// 定义一个委托类型,用于事件处理程序
public delegate void NotifyEventHandler(object sender, EventArgs e);
/// 发布者类
public class ProcessBusinessLogic
{
// 声明事件
public event NotifyEventHandler ProcessCompleted;
// 触发事件的方法
public virtual void OnProcessCompleted(EventArgs e)
{
// 执行这个事件
if (ProcessCompleted != null) // 如果事件已经绑定了需要执行的方法
{
ProcessCompleted?.Invoke(this, e);
}
}
// 模拟业务逻辑过程并触发事件
public void StartProcess()
{
Console.WriteLine("Process Started!");
// 这里可以加入实际的业务逻辑
// 业务逻辑完成,在这里触发事件
OnProcessCompleted(EventArgs.Empty);
}
}
// 订阅者类
public class EventSubscriber
{
// 在这里绑定事件
public void Subscriber(ProcessBusinessLogic process)
{
// 方式一
// 先创建委托示例,在这里,将名为ProcessCompleted的事件插入到Process_ProcessCompleted()方法中。 换句话说如果触发了NotifyEventHandler事件,则将执行Process_ProcessCompleted()方法。
process.ProcessCompleted += new NotifyEventHandler(Process_ProcessCompleted);
// 方式二 已经通过传参拿到ProcessBusinessLogic,所以不用new
//process.ProcessCompleted += Process_ProcessCompleted;
}
private void Process_ProcessCompleted(object sender, EventArgs e)
{
Console.WriteLine("Process Completed!");
}
}
class Program
{
public static void Main()
{
ProcessBusinessLogic process = new ProcessBusinessLogic();
EventSubscriber subscriber = new EventSubscriber();
// 订阅事件,
subscriber.Subscriber(process);
// 启动过程
process.StartProcess();
}
}
}
说明:
1、 定义委托
public delegate void NotifyEventHandler(object sender, EventArgs e);
这是一个委托类型,它定义了事件处理程序的签名。通常使用 EventHandler
或 EventHandler<TEventArgs>
来替代自定义的委托。
2、声明事件:这是一个使用 NotifyEventHandler 委托类型的事件。
public event NotifyEventHandler ProcessCompleted;
3、触发事件:这是一个受保护的方法,用于触发事件。使用 ?.Invoke
语法来确保只有在有订阅者时才调用事件。
protected virtual void OnProcessCompleted(EventArgs e)
{
ProcessCompleted?.Invoke(this, e);
}
4、订阅和取消订阅事件:
// 方式一
process.ProcessCompleted += Process_ProcessCompleted;
// 方式二
process.ProcessCompleted += new NotifyEventHandler(Process_ProcessCompleted);
订阅者使用 +=
运算符订阅事件,并定义事件处理程序 Process_ProcessCompleted
。
6.事件最常用的方式
// 事件最常用的方式:就是某个类中某些成员发生了变化,然后类的外面希望对这个类型中的成员进行监测;
var ed = new EventDemo();
// 注册事件,匿名方法
ed.MyValueChanged += () => { Console.WriteLine("value change observed"); };
ed.MyProperty = 10; // 对类型的属性进行set的时候就会触发绑定的属性;
ed.MyProperty = 10; // 对类型的属性进行set的时候就会触发绑定的属性;
class EventDemo
{
// 定义一个事件
public event Action MyValueChanged;
private int myValue;
public int MyProperty
{
get { return myValue; }
set
{
myValue = value;
// 这个属性进行set的时候就会触发这个事件
MyValueChanged?.Invoke();
}
}
}
事件作为类的成员,它的特殊性在于,它只能在类的内部使用
7.事件中的参数
在C#中,.Invoke()
方法是用来调用委托(Delegate)或事件(Event)的。在事件触发时,通常会使用 .Invoke()
来调用注册的事件处理器(Event Handler)。
事件的invoke方法是不公开的,我们通常通过+=或-=操作符来添加或移除事件处理程序。然而,在某些情况下,你可能需要直接调用事件的委托。在这种情况下,如果你正在使用事件的委托,并且委托的签名包含一个额外的this
参数,你需要明确提供这个参数。
this
关键字在这里表示当前对象的实例。在事件处理器中使用 this
通常是合适的,因为事件处理器是当前对象的一个方法。
EventArgs
是一个特殊的 EventArgs
实例,用于指示事件不包含任何数据。当事件处理器不需要任何额外的信息时,可以使用 EventArgs.Empty
。
例如,考虑以下事件委托:
public delegate void MyEventHandler(object sender, EventArgs e);
在这种情况下,sender
参数表示触发事件的对象实例。当你直接调用事件的委托而不是使用+=
或-=
操作符时,你需要手动传递这个参数。
例如,如果你有一个事件MyEvent
,并且你想要直接调用它的委托,你可以这样做:
public event MyEventHandler MyEvent;
// 调用事件的委托
MyEvent?.Invoke(this, EventArgs.Empty);
在这个例子中,this
关键字表示当前对象实例,它作为sender
参数传递给事件处理程序。
EventArgs
是当前事件的处理参数
请注意,直接调用事件的委托通常不是一个好的实践,因为它破坏了事件的封装性,使得事件的添加和移除可以在不经过正常的+=
和-=
操作的情况下进行。在大多数情况下,你应该仅通过操作符来管理事件。
更多推荐
所有评论(0)