- 委托:一种引用类型,这种类型可以用来定义方法签名,从而使用委托实现将方法作为参数传递给其他方法。类似于C++中的函数之争,使用委托使程序员可以将方法引用封装在委托对象内。
- 定义和声明委托:
1 delegate 返回值 委托名(参数列表); 2 eg: 3 public delegate void SayHelloDelegate(string name);
- 使用委托:委托其实通过返回值和参数列表来定义方法和签名。任何与委托具有相同返回值和参数列表(签名)的方法都可以赋给该委托。
1 public delegate void SayHelloDelegate(string name);//声明委托 2 public viod SayHelloEnglish(string name)//声明方法 3 { 4 Console.WriteLine("Hello,{0}",name); 5 } 6 SayHelloDelegate s=SayHelloEnglish;//把方法赋予委托对象
委托实例化的几种形式:
1 1.使用new关键字 2 SayHelloDelegate s=new SayHelloDelegate(SayHelloEnglish); 3 2.直接赋值 4 SayHelloDelegate s=SayHelloEnglish; 5 3.使用匿名方法 6 SayHelloDelegate s=delegate(string name) 7 { 8 Console.WriteLine("Hello,{0}",name); 9 } 10 4.使用Lambda表达式,例如: 11 SayHelloDelegate s=name=> 12 { 13 Console.WriteLine("Hello,{0}",name); 14 }
1 委托实例化后,就可以像调用方法一样调用委托。eg: 2 s("John");
- 多播委托:一个委托对象可以包含多个方法。调用多播委托时,按赋值的顺序一次执行每个方法。
- 实现多播委托
- 使用运算符+实现:委托对象的一个有用属性,可以使用+运算符将多个对象分配给一个委托实例。这里运算符+操作对象只能是委托对象,不能使方法签名。
1 public delegate void SayHelloDelegate(string name); 2 public void SayHelloEnglish(string name) 3 { 4 Console.WriteLine("Hello,{0}",name); 5 } 6 public void SayHelloChinese(string name); 7 { 8 Console.WriteLine("你好,{0}",name); 9 } 10 SayHelloDelegate s1=new SayHelloDelegate(SayHelloEnglish); 11 SayHelloDelegate s2=SayHelloChinese; 12 SayHelloDelegate s3=s1+s2;//操作符两边只能是委托对象
- 使用运算符+=
1 public delegate void SayHelloDelegate(string name); 2 public void SayHelloEnglish(string name) 3 { 4 Console.WriteLine("Hello,{0}",name); 5 } 6 public void SayHelloChinese(string name); 7 { 8 Console.WriteLine("你好,{0}",name); 9 } 10 SayHelloDelegate s=new SayHelloDelegate(SayHelloEnglish); 11 SayHelloDelegate s+=SayHelloChinese;//右边的操作对象是方法签名
- 使用运算符+实现:委托对象的一个有用属性,可以使用+运算符将多个对象分配给一个委托实例。这里运算符+操作对象只能是委托对象,不能使方法签名。
- 特点:
- 多播委托可以包含多个方法
- 多播委托包含的方法必须返回viod,否则会抛出run-time exception
- 只能合并相同类型的委托
- 从多播委托中移除组件
- 使用运算符-,两侧必须为委托对象
- 使用运算符-=,右侧可以为方法名
- 实现多播委托
- 匿名方法:相对于命名方法(以前接触的方法都是命名方法,都是包含方法名的)来说,顾名思义,就是没有命名的方法,就是作为一个整体的一些代码块。想要实现匿名方法,使用委托是唯一途径,把整个代码块作为参数赋予委托对象,然后调用委托对象,从而实现方法的执行。
1 //创建委托 2 delegate void Del(string name); 3 //实现匿名方法 4 Del d=delegate(string name) 5 { 6 7 //code block 8 };
summary:
- 匿名方法的定义是以关键字delegate开始,后面跟着参数列表和方法主体。
- 使用匿名方法,可以省去一个编写方法的过程,从而使代码看起来更简洁。
- 使用匿名方法可以减少实例化委托的代码系统开销。
attention:
- 在匿名方法中不能使用跳转语句跳转到该匿名方法的外部,匿名方法外部的跳转语句不能跳转到匿名方法的内部。
- 匿名方法内部不能访问不安全代码。
- 不能再匿名方法外部使用ref和out参数,但可以使用在匿名方法外部定义的其他变量。
1 public delegate void MyDelegate(); 2 class Program 3 { 4 static void Main(string[] args) 5 { 6 Program p=new Program(); 7 for(int i=1;i<=5;i++) 8 p.TestInstanceDataMembers(); 9 Console.ReadLine(); 10 } 11 //测试方法 12 public void TestInstanceDataMembers() 13 { 14 //声明匿名方法并赋值给对象 15 MyDelegate d=delegate 16 { 17 //操作外部局部变量 18 Console.WriteLine("Count:{0}",++m_iCount); 19 }; 20 d();//执行方法 21 } 22 public int m_iCount=0;//定义外部变量 23 } 24
1 Count: 1 2 Count: 2 3 Count: 3 4 Count: 4 5 Count: 5
- 委托中的协变和逆变:将方法签名与委托类型匹配时,协变和逆变提供了一定程度的灵活性。协变允许方法具有的派生返回类型比委托中定义的更多。逆变允许方法具有的派生参数类型比委托类型中的更少。
- 协变方法签名与委托类型匹配的灵活性体现在方法返回类型上。eg:
1 Shape 2 { 3 //类主体 4 } 5 Point:Shape 6 { 7 //类主体 8 } 9 delegate Shape DoDelegate(); 10 static Shape FirstDo() 11 { 12 return null; 13 } 14 static Point SecondDo() 15 { 16 return null; 17 } 18 DoDelegate Do1=FirstDo; 19 //协变允许实现这样的委托 20 DoDelegate Do2=SecondDo;
协变允许返回类型更多体现在类的派生上
- 逆变使方法签名与委托类型匹配的灵活性体现在参数上。eg:
1 System.DateTime lastActivity; 2 public From1() 3 { 4 InitializeComponent(); 5 lastActivity=new System.DateTime(); 6 this.textBox1.KeyDown+=this.MultiHandler;//触发KeyEventArgs 7 this.botton1.MouseClick+=this.MultiHandler;//触发MouseEventArgs 8 } 9 private void MultiHandler(object sender,System.EvenArgs e) 10 { 11 lastActivity=System.DateTime.Now; 12 }
以上代码中只创建一个接收EventArgs输入参数的事件处理程序,然后,可以将该处理程序与发送MouseEventArgs类型作为参数的Botton.MouseClick事件一起使用,也可以将该处理程序与发送KeyEventArgs参数的TextBox.KeyDown事件一起使用。
- 回调函数:委托实例化后,就可以将其作为参数进行传递,方法遍可以将一个委托作为参数来接受,并且以后可以调用该委托,这称为回调函数
1 delegate void Del(string name);//定义委托 2 void Do(string)//定义方法 3 { 4 } 5 void FirstDo(string s,Del del)//把委托作为参数传递进来 6 { 7 del(s);//调用委托 8 } 9 Del del=new Del(Do);//初始化委托 10 FirstDo(“你好",del);//调用方法
- 属于工作流的一个部分
- 必须按照工作流指定的调用约定来声明(定义)
- 它的调用时机由工作流决定,回调函数的实现者不能直接调用回调函数来实现工作流的功能。
- 使用回调机制,可以为工作流实现扩展。可以把工作流中需要用户干预的,或者需要提供给用户的数据以回调的模式提供给用户。而用户不需要知道整个工作的流程,只需要知道回调函数的说明就可以使用工作流模块提供的功能,这对信息的隐藏也是有作用的。
1 class SumClass 2 { 3 public delegate int Sum(int num1,int num2);委托 4 public int SumAll(int num1,int num2,Sum sun)//把委托实例作为参数传递 5 { 6 return sun(num1,num2);//回调函数 7 } 8 } 9 class Program 10 { 11 static void Main(string[] args) 12 { 13 Program prog=new Program(); 14 SumClass sumClass=new SumClass(); 15 int result=sunClass.SumAll(1,2,prog.GetSum);//回调函数GetSum 16 Console.WriteLine(result.ToString()); 17 Console.ReadLine(); 18 } 19 private int GetSum(int a,int b) 20 { 21 return a+b; 22 } 23 }
- 事件:所有与用户交互的过程基本上都是事件触发和处理的过程。
- 定义和声明事件:事件其实就是消息,事件的本质就是对消息的封装,用作对象之间的通信:事件的发起方称为事件发送器,事件的接收方称为事件的接收器。在C#中提供了一个名为EventHandler的预定义委托,专用于表示不生成数据的事件的事件处理方法。
这个委托的预定义如下: public delegate void EventHandler( object sender,//引用引发事件的实例 EventArgs e//从EventArgs类型派生,保存事件数据 )
EventHandler专用于表示不生成数据的事件的事件处理程序方法。如果事件生成数据,则必须提供自定义数据类型,并创建一个委托,其中第二个参数为自定义参数类型。若要将事件与处理事件的方法关联,还要向事件添加委托的实例,这时需要使用关键字event来声明。
1 //访问修饰符event EventHandler 事件名 2 3 public event EventHandler NoDataEventHandler; 4 //定义了没有数据的事件成员
- 定义事件处理程序:定义了事件成员,就可以把事件处理程序关联到事件上。事件处理程序委托会被绑定到系统引发事件时要执行的方法。事件处理程序会被添加到事件中,以便当事件引发时,事件处理程序能够调用它的方法,这就是订阅事件。订阅事件的处理过程如下:
- 编写事件处理程序
1 void HandleCustomEvent(object sender,CustomEventArgs a) 2 { 3 //处理程序 4 }
- 使用加法赋值运算符+=来为事件附加处理程序,eg:
1 publisher.RaiseCustomEvent+=HandlerCustomEvent;
当然如果用匿名方法或Lambda表达式,可以不用事先编写事件处理程序,直接把匿名方法或Lambda表达式绑定到事件上。事件是一种特殊类型的委托,支持多播委托,因此可以把多个事件处理程序绑定到一个事件中。
- 创建并使用事件
1 using cs002; 2 using System; 3 using System.Collections.Generic; 4 using System.Linq; 5 using System.Text; 6 using System.Threading.Tasks; 7 8 9 namespace cs002 10 { 11 public class A 12 { 13 public delegate void EventHandler(object sender, EventArgs e);//定义委托 14 public event EventHandler a;//声明事件 15 public void Run(EventArgs e)//引发事件 16 { 17 Console.WriteLine("产生一个事件。"); 18 a(this, e);//处理当前引发的事件 19 } 20 } 21 class B 22 { 23 public B(A a) 24 { 25 a.a += new A.EventHandler(this.b); //订阅事件处理程序 26 } 27 private void b(object sender,EventArgs e)//事件处理的方法 28 { 29 Console.WriteLine("处理事件!"); 30 Console.Read(); 31 } 32 } 33 class prg 34 { 35 public static void Main(string[] args) 36 { 37 A a = new A(); 38 B b = new B(a); 39 EventArgs e = new EventArgs(); 40 a.Run(e); 41 } 42 } 43 }
- 从EventArgs类派生:EventArgs类是包含事件数据的类的基类。此类不包含事件数据,在事件的引发时不向事件处理程序传递状态信息的事件会使用此类。EventArgs类本身并没有什么可介绍的,它只提供了一个只读字段Empty,表示事件没有数据。如果事件处理需要状态信息,则应用程序必须从EventArgs类派生一个类来保存数据。例如创建一个KeyEventArgs类,该类保存键盘按键事件中要包含的按键信息。
1 internal class KeyEventArgs:EventArgs 2 { 3 private char keyChar;//按键信息 4 public KeyEventArgs(char keyChar):base() 5 { 6 this.keyChar=keyChar; 7 } 8 public char KeyChar//按键信息 9 { 10 get 11 { 12 return keyChar; 13 } 14 } 15 }
创建并使用派生事件
1 using cs002; 2 using System; 3 using System.Collections.Generic; 4 using System.Linq; 5 using System.Text; 6 using System.Threading.Tasks; 7 8 9 namespace cs002 10 { 11 //触发火警事件 12 public class FireEventArgs:EventArgs 13 { 14 public FireEventArgs (string room,int ferocity) 15 { 16 this.room = room; 17 this.ferocity = ferocity; 18 } 19 public string room; 20 public int ferocity;//火势等级 21 } 22 public class FireAlarm 23 { 24 public delegate void FireEventHandler(object sender, FireEventArgs fe); 25 //创建火警事件委托 26 public event FireEventHandler FireEvent; 27 //触发火警事件 和委托相联系/类型相同 28 public void ActivateFireAlarm(string room,int ferocity) 29 { 30 FireEventArgs fireArgs = new FireEventArgs(room, ferocity); 31 //调用委托 32 FireEvent(this, fireArgs); 33 } 34 } 35 //火警事件处理类 36 class FireHandlerClass 37 { 38 public FireHandlerClass (FireAlarm fireAlarm) 39 { 40 //订阅火警事件处理程序 41 fireAlarm.FireEvent += new FireAlarm.FireEventHandler(ExtinguishFire);//多播委托 42 } 43 //火警事件处理程序 44 void ExtinguishFire(object sender,FireEventArgs fe) 45 { 46 Console.WriteLine(" 火警事件是由{0}引发的:", sender.ToString()); 47 if (fe.ferocity < 2) 48 Console.WriteLine("发生在{0}的火警是没有问题的,用水就可以浇灭。", fe.room); 49 else if (fe.ferocity < 5) 50 Console.WriteLine("要使用灭火器才能扑灭{0}的大火。", fe.room); 51 else 52 Console.WriteLine("发生在{0}的大火已经失控,请通知政府部门!", fe.room); 53 } 54 } 55 public class program 56 { 57 public static void Main(string[] args) 58 { 59 //创建火警对象 60 FireAlarm myFireAlarm = new FireAlarm(); 61 //创建火警事件处理程序对象 62 FireHandlerClass myFireHandler = new FireHandlerClass(myFireAlarm); 63 //触发火警事件 64 myFireAlarm.ActivateFireAlarm("厨房", 3); 65 myFireAlarm.ActivateFireAlarm("书房", 1); 66 myFireAlarm.ActivateFireAlarm("车库", 5); 67 } 68 } 69 }
- 在派生类中引发基类事件:事件作为类的成员,一般都被定义为public类型,但是派生类并不能直接调用基类中声明的事件。但可以在包含该事件的基类中创建一个受保护的调用方法。通过调用或重写此调用方法,派生类便可简介调用该事件。
1 using cs002; 2 using System; 3 using System.Collections.Generic; 4 using System.Linq; 5 using System.Text; 6 using System.Threading.Tasks; 7 8 9 namespace cs002 10 { 11 public class ShapeEventArgs:EventArgs 12 { 13 private double newArea; 14 public ShapeEventArgs(double d) 15 { 16 newArea = d; 17 } 18 public double NewArea 19 { 20 get { return newArea; } 21 } 22 } 23 //定义图形类Shape,在该类中定义事件 24 public abstract class Shape 25 { 26 public delegate void shapeEventHandler(object sender, ShapeEventArgs e); 27 public double area; 28 public event shapeEventHandler ShapeChanged; 29 public abstract void Draw(); 30 //受保护的触发事件的方法 31 protected virtual void OnShapeChanged(ShapeEventArgs e) 32 { 33 shapeEventHandler handler = ShapeChanged; 34 if(handler!=null) 35 { 36 handler(this, e); 37 } 38 } 39 } 40 public class Circle:Shape 41 { 42 private double radius; 43 public Circle(double d) 44 { 45 radius = d; 46 area = 3.14 * d * d; 47 } 48 public void Update(double d) 49 { 50 radius = d; 51 area = 3.14 * d * d; 52 OnShapeChanged(new ShapeEventArgs(area));//触发事件 53 } 54 //重写受保护的触发事件的方法 55 protected override void OnShapeChanged(ShapeEventArgs e) 56 { 57 //调用基类的方法以触发事件 58 base.OnShapeChanged(e); 59 } 60 public override void Draw() 61 { 62 Console.WriteLine("画一个圆"); 63 } 64 } 65 //事件处理程序 66 public class ShapeHandler 67 { 68 public ShapeHandler (Shape s) 69 { 70 //订阅事件 71 s.ShapeChanged += new Shape.shapeEventHandler(HandleShapeChanged); 72 } 73 //事件处理程序 74 private void HandleShapeChanged(object sender,ShapeEventArgs e) 75 { 76 Shape s = (Shape)sender; 77 //显示图形信息 78 Console.WriteLine("图形更改事件是由{0}引起的,图形的面积更新后是:{1}", sender.ToString(), e.NewArea); 79 //绘制图形 80 s.Draw(); 81 } 82 } 83 public class program 84 { 85 public static void Main(string[] args) 86 { 87 Circle c1 = new Circle(54); 88 Console.WriteLine("更新前的图形面积是:{0}", c1.area); 89 ShapeHandler myShapeHandler = new ShapeHandler(c1); 90 c1.Update(57); 91 System.Console.WriteLine("Press any key to exit."); 92 System.Console.ReadKey(); 93 } 94 } 95 }
- 实现接口事件
1 public interface Iobject//定义接口 2 { 3 event EventHandler OnChangedEventArgs; 4 } 5 public class MyEventArgs:EventArgs 6 { 7 } 8 public class A:Iobject 9 { 10 public event EventHandler OnChangedEventArgs; 11 void ChangedA{ 12 OnChanged(new MyEventArgs(/*arguments*/)); 13 } 14 protected virtual void OnChanged(MyEventArgs e) 15 { 16 if(OnChangedEventArgs!=null) 17 { 18 OnChangedeEventArgs(this,e); 19 } 20 } 21 }