委托
如果我们想 传递一个函数作为参数 怎么办? C# 如何处理回调函数或事件处理程序? 答案是——委托
委托的核心作用是:把函数作为参数传递给委托,可通过委托实例加和累计多个函数,按流程帮我处理
委托是定义方法签名的引用类型数据类型。 您可以定义委托的变量,就像其他数据类型一样,可以引用与委托具有相同签名的任何方法。
我们使用委托时会涉及到三个步骤:
- 声明一个委托 Delegate
- 设置目标方法
- 调用委托 Delegate
声明委托
可以使用 delegate
关键字后跟函数签名来声明委托,如下所示。
[访问修饰符] delegate [返回类型] [委托名称]([参数...])
下面声明了一个名为 MyDelegate 的委托。
public delegate void MyDelegate(string msg);
上面,我们已经声明了一个具有 void 返回类型和字符串参数的委托 MyDelegate。 委托可以在类外或类内声明。 推荐:声明在类之外。
声明委托后,我们需要设置目标方法或 lambda 表达式。 我们可以通过使用 new 关键字创建委托对象并传递签名与委托签名匹配的方法来实现。
委托的使用
using System;
namespace ConsoleApp1
{
// 1. 声明一个委托
public delegate void MyDelegate(string msg);
class Program
{
// 2. 设置跟 MyDelegate 有相同参数和返回值的方法
static void MethodA(string message)
{
Console.WriteLine(message);
}
static void Main(string[] args)
{
// a. 把函数传给委托
MyDelegate del1 = new MyDelegate(MethodA);
// b. 直接赋值给委托
MyDelegate del2 = MethodA;
// c. 使用 lambda 表达式
MyDelegate del3 = (string msg) => Console.WriteLine(msg);
// 3. 调用委托
del1.Invoke("1"); // a. 使用 Invoke() 方法调用委托
del2("2"); // b. 使用 () 运算符调用委托
del3("3");
Console.ReadKey();
}
}
}
运行结果:
1
2
3
多播委托
委托可以 引用多个方法。当调用多播委托时,它将依次调用所有关联的方法。要将多个方法分配给同一个委托实例,可以使用 +=
运算符
using System;
delegate int NumberChanger(int n);
namespace DelegateAppl
{
class TestDelegate
{
static int num = 10;
public static int AddNum(int p)
{
num += p;
return num;
}
public static int MultNum(int q)
{
num *= q;
return num;
}
public static int getNum()
{
return num;
}
static void Main(string[] args)
{
// 创建委托实例
NumberChanger nc;
NumberChanger nc1 = new NumberChanger(AddNum);
NumberChanger nc2 = new NumberChanger(MultNum);
nc = nc1;
nc += nc2;
// 调用多播
nc(5);
Console.WriteLine("Value of Num: {0}", getNum());
Console.ReadKey();
}
}
}
运行结果:
Value of Num: 75
将委托(函数)作为参数传递
在c#里面,有的时候一些代码的实现必须用到委托,比如:
- 线程里面修改某个textBox的值,如果直接在线程里面写修改的代码,执行时候,编译器会报错,因为c#不允许这样写。
- 还有在单独写的类里面,修改某个form里面某个控件的属性值等等,也是不被允许的。
这时候,就需要使用到委托(delegate)。委托其实是这样的,为某些实现写一个函数,并将其赋值给委托(相当于函数指针),在使用的时候直接通过 委托名 来调用。
如果我们要把方法当做参数来进行传递的话,就要用到委托。 简单来说,委托是一个类型,这个类型可以赋值一个方法的引用。这是委托的主要用途
委托的理解是:委托别人帮我做事,那别人就得提前想(写)好方法才能帮我干活
using System;
class Program
{
// 1. 定义委托
public delegate void GreetDelegate(string name);
// 2. 设置跟 GreetDelegate 有相同参数和返回值的方法
private static void EnglishGreeting(string name)
{
Console.WriteLine("Morning, " + name);
}
private static void ChineseGreeting(string name)
{
Console.WriteLine("早上好," + name);
}
// 3. 定义一个接收【name + GreetingDelegate】类型的方法
private static void GreetPeople(string name, GreetDelegate MakeGreeting)
{
MakeGreeting(name);
}
static void Main(string[] args)
{
// 4. 将方法作为参数传递,并同时传递方法执行需要的参数
GreetPeople("bro", EnglishGreeting);
GreetPeople("兄弟", ChineseGreeting);
Console.ReadKey();
}
}
运行结果:
Morning, bro
早上好,兄弟
事件
事件(Event)基本上说是某个【用户操作】,如按键、点击、鼠标移动等等,或者是一些【提示通知】,如系统生成的通知。应用程序需要在事件发生时响应事件
C# 中使用事件机制实现 线程间的通信。
通过事件使用委托
事件在类中声明且生成,且通过使用同一个类或其他类中的委托与事件处理程序关联。包含事件的类用于发布事件。这被称为 发布器 类。其他接受该事件的类被称为 订阅器) 类。事件使用 发布-订阅 模型。
发布器 是一个包含事件和委托定义的对象。事件和委托之间的联系也定义在这个对象中。发布器(publisher)类的对象调用这个事件,并通知其他的对象。
订阅器 是一个接受事件并提供事件处理程序的对象。在发布器(publisher)类中的委托调用订阅器(subscriber)类中的方法(事件处理程序)。
声明事件
在类的内部声明事件,首先必须声明该事件的委托类型。例如:
public delegate void BoilerLogHandler(string status);
然后,声明事件本身,使用 event 关键字:
// 基于上面的委托定义事件
public event BoilerLogHandler BoilerEventLog;
上面的代码定义了一个名为 BoilerLogHandler 的委托和一个名为 BoilerEventLog 的事件,该事件在生成的时候会调用委托。
实例
using System;
namespace SimpleEvent
{
using System;
/***********发布器类***********/
public class EventTest
{
private int value;
public delegate void NumManipulationHandler();
public event NumManipulationHandler ChangeNum;
protected virtual void OnNumChanged()
{
if ( ChangeNum != null )
{
ChangeNum(); /* 事件被触发 */
} else {
Console.WriteLine( "event not fire" );
Console.ReadKey(); /* 回车继续 */
}
}
public EventTest()
{
int n = 5;
SetValue( n );
}
public void SetValue( int n )
{
if ( value != n )
{
value = n;
OnNumChanged();
}
}
}
/***********订阅器类***********/
public class subscribEvent
{
public void printf()
{
Console.WriteLine( "event fire" );
Console.ReadKey(); /* 回车继续 */
}
}
/***********触发***********/
public class MainClass
{
public static void Main()
{
EventTest e = new EventTest(); /* 实例化对象,第一次没有触发事件 */
subscribEvent v = new subscribEvent(); /* 实例化对象 */
e.ChangeNum += new EventTest.NumManipulationHandler( v.printf ); /* 注册 */
e.SetValue( 7 );
e.SetValue( 11 );
}
}
}
委托和事件的区别
委托和事件有一定的相似性,这里先说一下委托和事件的定义
委托:delegate 是一种可用于封装命名或匿名方法的引用类型。 委托类似于 C++ 中的 函数指针;但是,委托是类型安全和可靠的。
事件:事件是特殊类型的多路广播委托,仅可从声明它们的类或结构里面调用。 如果其他类或结构订阅了该事件,则当发行者类引发该事件时,会调用其事件处理程序方法。
委托和事件的区别如下:
区别 | 委托 | 事件 | |
---|---|---|---|
1 | 是否是一个类型 | 是 | 否,事件修饰的是一个对象 |
2 | 是否可以在类外部进行调用 | 是 | 否 |
3 | 是否可以使用=来赋值 | 是 | 否 |
区别一:是否是一个类型
class Class3
{
static void Main(string[] args)
{
//委托正确使用
Class1.NumberChanger n1 = Class2.ClassA_Test;
//事件使用 编译器报错
Class1.changer handle2 = Class2.ClassA_Test;
}
}
错误:Class1.changer 是“字段”,但此处被当做“类型”来使用
区别二:委托可以在声明它的类外部进行调用,而事件只能在类的内部进行调用。
(1)在类外部调用委托
(2)在类外部调用事件
事件“ClassC.Say_EventHandler”只能出现在 += 或 -= 的左边(从类型“ClassC”中使用时除外)
区别三:委托可以在外部类使用 = 来赋值,事件只能在内部类用 = 赋值,外部类不可以
从编译器提示的错误,我们可以了解到,事件只能在声明它的类内部被调用。从事件本身来讲,事件一般用于类自身的属性变化时,用来通知外界自身的变化的。我们将对ClassC内部的一个属性赋值,然后调用事件,模拟对外通知。代码如下所示
总结:事件与委托最主要的区别应该是不能在外部调用,但可以通过+=或-=进行注册,但如果委托变量为私有,则外部不能注册;如果为公有,则外部有可以调用,破坏了封装,所以没办法,在这种情况就定义一个event就好了