详解C#委托和事件(一)
委托(Delegate)是安全封装方法的类型,类似于C和C++中的函数指针,与函数指针不同的是,委托是面向对象的、类型安全的和可靠的;
一、委托类型是CTS中五种基础类型之一,是一种引用类型,表示对具有指定参数列表和返回类型的方法的引用,也是一种特殊的类类型,其类型为System.MulticastDelegate,继承自抽象类System.Delegate;使用委托类型声明的对象,即委托实例,是一种特殊类型的对象,其可以与任何该委托所能封装的方法相关联,然后通过委托实例调用这些方法;
※在命名空间下声明的委托类型与类类型相似,只能为public或internal,在类内部声明的委托类型为当前类的嵌套类型,可以指定各种访问修饰符;在类内部声明的委托实例是一个字段,在方法内部声明的委托实例是一个局部变量;
1.委托的声明与类的声明方式相似,委托的类型由声明委托的名称确定:
//声明一个自定义的委托类型MyDelegate,可以封装无参数列表,无返回值的方法
public delegate void MyDelegate();
2.每个委托类型都描述其所能封装的方法的参数列表(参数数目和类型)以及返回值类型,每当需要封装一组新的参数列表或返回值类型的方法时,都必须声明一个新的委托类型;
委托实例的声明与对象的声明方式相似,由委托类型和委托实例的名称组成:
//声明一个MyDelegate类型的委托实例myDelegate
private MyDelegate myDelegate;
3.声明和实例化委托时,可以使用new关键词、直接用方法名赋值、匿名方法或者Lambda表达式;
4.委托的一个重要特性是可以使用+或+=运算符将多个具有相同参数列表和返回值类型的方法或相同类型的委托实例添加到一个委托实例中,使用-或-=运算符可以从委托实例中移除指定的方法或委托实例,调用该委托实例时,会调用其中的所有方法,这个特性被称为多播委托(Multicast Delegates),广泛应用于事件中;
多播委托内包含已赋值委托的列表,在调用多播委托时,它会按顺序调用列表中的委托;对于有返回值的多播委托,其返回值是按顺序执行列表后最后一个方法的返回值;
public delegate void MyDelegate(); public class MyClass { public void MyFunc() { Console.WriteLine("MyFunc Run"); } } public class Program { static void Main(string[] args) { MyClass myClass = new MyClass(); MyDelegate myDelegate; //1.使用new创建委托对象时指定方法 myDelegate = new MyDelegate(myClass.MyFunc); //先用=运算符创建实例,=后面同样可以使用以下任何一种方式进行赋值 //2.直接用方法名赋值 myDelegate += myClass.MyFunc; //3.使用匿名方法赋值 myDelegate += delegate () { Console.WriteLine("Anonymous methods run"); }; //4.使用Lambda表达式赋值 myDelegate += () => { Console.WriteLine("Lambda run"); }; //5.使用相同类型的委托实例赋值 myDelegate += new MyDelegate(myClass.MyFunc); //6.使用-=运算符删除委托中指定的方法 myDelegate -= myClass.MyFunc; //※只能通过此种方式才可以将委托列表中指定方法移除,不能通过myClass = null等方式移除 //执行委托中的方法 myDelegate(); //或者通过其Invoke()方法执行委托 //myDelegate.Invoke(); //增加非空判断 //if (myDelegate != null) myDelegate(); //或者简化为(需要C#6.0以上) //myDelegate?.Invoke(); Console.ReadKey(); } }
※可以将类或结构中任何与委托类型的参数列表和返回值类型相匹配的实例方法和静态方法赋值给委托实例;
※使用匿名方法或Lambda表达式添加的方法不能通过-或-=删除这些方法;
※与直接调用方法相比,使用委托调用方法用时几乎没有差别,使用以下代码多次测试:
public delegate void MyDelegate(); public class MyClass { public void MyFunc() { } } class Program { static void Main(string[] args) { MyClass myClass = new MyClass(); MyDelegate myDelegate = myClass.MyFunc; Stopwatch stopwatch = new Stopwatch(); stopwatch.Start(); for (int i = 0; i < 10_000_000; i++) { myDelegate(); } stopwatch.Stop(); Console.WriteLine($"使用委托调用1千万次耗时:{stopwatch.ElapsedMilliseconds}"); stopwatch.Reset(); stopwatch.Start(); for (int i = 0; i < 10_000_000; i++) { myClass.MyFunc(); } stopwatch.Stop(); Console.WriteLine($"直接调用1千万次耗时:{stopwatch.ElapsedMilliseconds}"); Console.Read(); } }
二、委托实例是一种特殊类型的对象,其特殊之处在于,其他对象都包含数据,而委托实例中只包含一个或多个方法的引用,也正是因为这个特性,通常用委托实例来传递方法,即通过委托实例作为载体将方法作为参数传递到另一个方法中,并在方法中的某个时机或稍后调用委托实例来执行这个方法,这被称为异步回调,作为参数的这个方法也叫回调函数(Callback Method);
1.所有方法都可以直接隐式转换为对应参数列表和返回值类型的委托:
public void MyFunc(Action<int> myAction, int myNum) { if (myAction != null) { myAction(myNum); } } public void MyAction(int myNum) { Console.WriteLine("myNum is : " + myNum); } //使用方式: MyFunc(MyAction, 1); //myNum is : 1
2.方法不能显示转换为object类型,但委托可以,所以可以将方法显示转换为委托然后作为参数传递:
public void MyFunc(object myObj, int myNum) { Action<int> myAction = (Action<int>)myObj; myAction(myNum); } public void MyAction(int myNum) { Console.WriteLine("myNum is : " + myNum); } //使用方式: MyFunc((Action<int>)MyAction, 1); //myNum is : 1
三、对于有返回值的多播委托,如果想保存其返回值列表,可以先获取委托实例中的方法,然后依次执行这些方法:
Func<string> myFunc; Delegate[] delegateArray = myFunc.GetInvocationList(); string[] returnArray = new string[delegateArray.Length]; for (int i = 0; i < delegateArray.Length; i++) { returnArray[i] = (delegateArray[i] as Func<string>)(); //returnArray[i] = (string)delegateArray[i].DynamicInvoke(); }
四、引用System命名空间后,可以使用系统定义的两个泛型委托void Action()和TResult Func<TResult>(),其中,Action()为无返回值的委托,Func<TResult>()为有返回值且返回值类型为TResult的委托,两者都可以拥有多个参数,例如:void Action<T1, T2>(T1 obj1, T2 obj2)和TResult Func<T1, T2, TResult>(T1 obj1, T2 obj2);
1.声明一个多个参数的委托类型:
public Action<string, int> MyAction; //声明一个无返回值,参数列表为string,int的委托
public Func<bool, string, int> MyFunc; //声明一个返回值类型为bool,参数列表为string,int的委托
2.System命名空间还提供了一个返回bool值的委托bool Predicate<T>(T obj),通常用于匹配相关的操作;
五、事件(Event)是一种具有一定限制的特殊的多播委托:
//首先声明一个委托或使用系统预留的委托类型Action等 public delegate void MyDelegate(); //声明事件,相比声明委托实例多了一个event关键字 public event MyDelegate MyEvent;
1.委托和事件的关系类似变量和属性的关系,委托可以作为局部变量,也可以作为类的成员,而事件只能作为类的成员;委托可以在任何地方进行赋值(=)和调用操作,而事件仅可以从声明事件的类或结构中对其进行赋值和调用操作,在外部只能进行+=添加方法和-=移除方法操作;
2.通常,委托用于回调,将方法当做参数传递到其他方法中,并在指定的时机调用该委托中的方法;事件用于通知,在类或对象中通知其他类或对象以进行相关操作,即接收方将需要响应的方法注册到源对象的事件上,当源对象发生了某个特定情况时,触发该事件,此时该事件所注册的所有方法都会被调用;
public class MyClass { public delegate void MyDelegate(); public event MyDelegate MyEvent; public void OnEvent() { //只可在当前类中进行调用 MyEvent?.Invoke(); //只可在当前类中进行赋值 //MyEvent = null; } } //使用方式: MyClass myClass = new MyClass(); //在外部使用+=给事件添加方法 myClass.MyEvent += MyFunc; //方法定义:void MyFunc() { Console.WriteLine("MyFunc run"); } myClass.OnEvent(); //MyFunc run
3.事件中方法的添加和移除是通过事件访问器实现的,其形式类似属性访问器,不同之处在于事件访问器命名为add和remove,在默认情况下,不需要手动定义事件访问器,编译器会自动添加默认的事件访问器,也可以进行自定义事件访问器:
private event MyDelegate myEvent; public event MyDelegate MyEvent { add { myEvent += value; } remove { myEvent -= value; } }
4.在使用反射获取类型的所有方法时,如果类型中包含事件,会获取事件中的公共访问器所生成的方法:
typeof(MyClass).GetMethods(); //add_MyEvent remove_MyEvent ToString Equals GetHashCode GetType
如果您觉得阅读本文对您有帮助,请点一下“推荐”按钮,您的认可是我写作的最大动力!
作者:Minotauros
出处:https://www.cnblogs.com/minotauros/
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。