“委托”基础 |
含义:
详解委托类型:
- 委托(Delegate):是.NET Framework对C#和VB.NET等面向对象编程语言特性的一个重要扩充。
- 是.NET中的一些重要技术,如事件、异步调用和多线程开发的技术基础。
实例:FirstDelegateExample
- 定义一个MathOpt类,类中有一个 public int Add(int x, int y) 的方法。
- 定义一个委托类型,它有两个int类型的参数,返回一个int类型的值(public delegate int MathOptDelegate(int value1, int value2);),其参数与返回值与上面定义的方法一致。
- 在Porgram(class Program)类中的Main方法中,定义MathOptDelegate类型的委托变量(委托是一种用户自定义的数据类型,可以用于定义变量);
- 委托变量接收一个方法的引用,调用时像普通方法一样传入参数就可以使用。
结论:
- 委托可以看成是一个方法的“容器”,将某一具体的方法“装入”后,就可以把它当成方法一样调用。
- 一个委托类型的变量,可以引用任何一个满足其要求的方法(是方法,而不是数据某一类型的变量,类似于C语言的“函数指针”,但又不仅仅是“函数指针”,还可以实现异步调用)。
详解委托类型:
委托的组合与分解
- 一个委托变量可以使用“+=”不断“挂接”多个方法,也能使用“-=”动态移除某个方法引用。
- 引用多个方法的委托变量称为“多路委托”。
- 调用该委托时,委托调用列表中的方法会依次执行。
实例:MulticastDelegateInvocation
- 声明一个委托(public delegate int MyDelegate(int value););
- 定义一个MyClass类,类中的两个方法 Func1 和 Func2 符合委托的要求;
- 在Program类中,定义 MyDelegate 委托变量del1,挂接方法(MyDelegate del1 = obj.Func1; del1 += obj.Func2;);
- 获取委托变量的方法调用列表(Delegate[] ds = del1.GetInvocationList(););
- 调用(del1(5);),将先调用 obj.Func1(),再调用 obj.Func2();
- 再定义一个 MyDelegate 委托变量 del2,并挂接方法;
- 组合委托:MyDelegate mul = del1 + del2;(其作用是首尾拼接成一个新的委托变量,方法列表中拥有4个方法)
从前面的例子看,使用委托只要有这几步:
- 定义委托类型;
- 定义一个或多个符合委托类型要求的方法;
- 定义委托类型的变量;
- 将第2步定义的方法引用使用“+=”“挂接”到第3步定义个变量,以形成一个“方法调用列表”;
- 通过委托变量“间接”调用方法调用列表。
- 可见,比较麻烦,也可以使用“匿名方法”进行简化
匿名方法(其实是将方法定义与委托变量赋值连个步骤合并):
- 定义一个委托(public delegate int AddDelegate(int i, int j););
- 利用匿名方法的特性直接给委托变量赋值(AddDelegate del = delgate(int i, int j) { 处理并放回int值; } )。
匿名方法的使用:
- 委托类型作形参:
- 使用委托类型参数的方法:public static void invokeDelegate(AddDelegate del, int i ,int j) { 处理; }
- 直接将匿名方法作为函数参数:invokeDelegate(delegate(int i, int j) { 处理并返回int值; }, 100, 200);
- 或者使用Lambda表达式(匿名方法的简写)进一步简化:invokeDelegate((i, j) => i + j, 100, 200);
在编程中应用委托
- 委托的本质特征是“一对多”(一个委托变量对应多个方法)。
实例:定时回调 UseTimeCallback(每各一秒显示当前时间)
.NET基类库中的委托应用 |
- 泛型委托
示例:
- 定义泛型委托:public delegate T MyGenericDelegate<T>(T obj);;
- 使用匿名方法给泛型委托变量赋值:MyGenericDelegate<int> del = delegate(int value){ 处理并返回int值; };;
- 或使用Lambda表达式给放行委托变量赋值:MyGenericDelegate<int> del2 = (value) => value * 2;;
- 调用泛型委托变量引用匿名方法:del(100);。
- .NET基类库中预定义的委托
例如:
- Func<> 委托:接收有返回值的方法
-
- 应用实例:
-
- 使用匿名方法:Func<int, int, long> add = delegate(int a, int b){ 处理并返回long值; };;
- 使用Lambda表达式:Func<int ,int ,int> subtract = (a, b) => a - b;
- 调用:add(5, 10); subtract(10, 5);
- 使用预定义的委托,可以省去反复定义委托类型的麻烦。
- Action<> 委托:接收没有返回值的方法
- 掌握委托是.NET程序员的基本要求!
委托与事件 |
事件
- 在“事件驱动”的软件系统中,符合某种预设条件的情形出现时,一个事件被触发。
- 事件三要素:
-
- 事件源,即激发事件的对象;
- 事件信息,即事件本身所携带的信息;
- 事件响应者,即响应事件的代码,这些代码决定了当事件发生时,需哟啊计算机完成的工作。
实例:鼠标坐标实时显示
- “事件驱动”的软件运行方式:
-
- 当某个事件触发时,程序员事先写好的响应此事件的代码被调用。
- “事件驱动”的软件开发方式:
-
- 定义并实现自己的事件,或从系统组件库中选择一种现成的事件;
- 为这一事件编写响应代码。
自定义事件
- 事件的主要特点为一对多关联,即一个事件源,可以有多个响应者;
- 委托与它所引用的方法也具有一对多的关联,因此启发:将事件看成是一个多路委托变量,而事件的响应方法则被此多路委托变量所引用。
- 使用委托实现自定义事件:
- 定义事件委托:public delegate void MyEventDelegate(int value);(事件携带着int value信息)
- 定义事件发布者类:public class Publisher{ 使用多路委托变量保存多个事件响应者的方法引用:public MyEventDelegate MyEvent; }
- 定义事件响应者:public class Subdcriber{ 事件触发时的回调方法:public void MyMethod(int value){xxx} }
- 使用关键字event定义自定义事件:
- 定义事件发布者类:public class Publisher{}
- 使用关键字event定义一个事件:public event MyEventDelegate MyEvent;
- 激发事件:public void FireEvent(int EnentArgu){ if (MyEvent != null) MyEvent(EventArgu); }
- 定义事件发布者类:public class Publisher{}
- event关键字定义的事件只能由事件源对象自己激发,外界无法通过访问委托变量直接激发事件。
.NET事件机制剖析 |
- 场景:当在windows Forms 应用程序中给某个控件事件编写了事件响应方法时,但又在代码编辑器中删除了这个方法,项目编译将失败。
- 通过测试跟踪得出:
- Click事件的定义如:public event EnventHandler Click;
- Click事件的响应方法必须符合EventHandler委托的要求:public delegate void EventHandler(object sender(事件源), EventArgs e(事件信息))
- 结论:.NET事件触发与响应机制是建立在委托之上的。
实例:
- 运行时设定事件的响应函数
-
- public partial class frmMain: Form
- {
-
- public frmMain()
- {
-
- InitializeComponent();
- //运行时才设定的事件响应函数
- button1.Click += new EnentHandler(button1_Click);
- }
- void button1_Click(object sender, EnentArgs e)
- {
-
- MessageBox.Show("I'm Clicked!");
- }
- }
- 实例二:动态挂接代码
- 实现:
- 在frmMain类中,定义字段:
- private int LoanMoney = 0; //借的钱
- private int LoanCount = 0; //借钱次数
- 在frmMain类中, 定义借钱的方法:private void LoanFormHuang()
- btnSum.Enable = true;
- LoanMoney = 0;
- btnSum.Click += new EnentHandler(btnSum_Click); //追加事件响应函数
- lblLoanCount.Text = string.Format("{0}次", ++LoanCount);
- 在frmMain类中,定义查看“杨白劳欠了黄世仁多少钱”的方法:private void ShowLoanState()
- LoanMoney += 100; //每次点击按钮增加100元
- lblLoanMoney.Text = string.Format("{0}元", LoanMoney);
- lblLoanMoney.Refresh();
- Thread.Sleep(300); //使其有动画效果
- btnSum.Enabled = flase;
- 在frmMain类中,定义:private void btnLoanFromHuang_Click(object sender, EventArgs e)
- LoanFromHuang();
- 在frmMain类中,定义:void btnSum_Click(object sender, EventArgs e)
- 实例三:使用一个方法响应多个对象的同种类型的事件:按回车焦点直接切换下一个输入框中
- 实现:
- 将“回车移动焦点”封装成一个事件响应函数:private void EnterToTab(object sender, KeyEventArgs e)
- groupBox1.SelectNextControl(sender as Control, true, true, true, true);
- 设置拥有焦点的文本框自动全选:
- txt = (sender as TextBox);
- if(txt != null) txt.SelectAll();
- 在窗体的构造函数(public frmEnterToTab())中:
- InitializeComponent();
- 挂接事件,把事件响应函数挂接到所有文本框上:
- foreach(Control ctl in groupBox1.Controls)
- {
- if (ctl is TextBox)
- (ctl as TextBox).keyDown += this.EnterToTab;
- if (ctl is TextBox)
- }
.NET自定义事件实用开发指导 |
技术场景:
- 设计一个可统计点击次数的按钮,它提供一个自定义的MyClick事件
- 实现方法一:
- 定义一个事件参数类 MyClickEventArgs,从EventArgs类派生,它所携带的事件信息是:按钮的单击次数 ClickCount
- 为参数类定义一个事件委托:
- 继承Button类实现在普通按钮的基础上添加“统计点击次数”的功能:
- 重写OnClick方法:
- 将该控件编译好后,其将作为新控件出现在工具栏上。
- 实现方法二:使用泛型事件委托类型(为了简化自定义事件开发,.NET基类库中定义了一个通用的泛型事件委托)