.NET 中的委托
1.1.1 定义
委托是一种引用方法的类型。一旦为委托分配了方法,委托将与该方法具有完全相同的行为。委托方法的使用可以像其他任何方法一样,具有参数和返回值,如下面的示例所示:
//Code in C#
public delegate int PerformCalculation(int x, int y);
与委托的签名(由返回类型和参数组成)匹配的任何方法都可以分配给该委托。
简单理解Delegate委托(或代理)是一种数据类型:它的变量可以引用到某一个符合要求的方法上,通过委托可以间接地调用该方法。
其实.NET的委托类似于C语言的函数指针,区别在于.NET委托是类型安全的,这说明,C中的函数指针只不过是一个指向存储单元的指针,我们无法说出这个指针实际指向什么。
1.1.2 委托使用
- 使用委托的四部曲:
- 定义一种委托类型
- 委托执行时要调用方法
- 定义一个委托实例
- 委托实例的调用
我们先定义一种委托类型如下:
- //自定义一种委托类型
- public delegate void StringProcessor(string input);
- 然后我们再定义5中候选的委托方法如下:
- void PrintString(string x)
- void PrintInteger(int x)
- void PrintTwoStrings(string x, string y)
- int GetStringLength(string x)
- void PrintObject(object x)
大家猜猜看哪个和上面提供的委托类型签名匹配(签名匹配:参数类型,参数个数和返回类型匹配)。激动时刻到了马上公布答案,和委托类型匹配的方法是PrintString和PrintObject,如果有不明白的请细细考虑一下委托匹配的条件—签名匹配。
图1委托成功输出
现在对委托有了一定的认识,接下来我们将介绍委托最经常使用的地方—事件。
我们将从发送器和接受器的角度讨论事件,例如在UI编程中,鼠标单击或键盘按键,发送器就是.NET的CLR,注意事件发送器并不知道接收器是谁,这符合面向对象的原则,而且某个事件接收器有个方法处理该事件,这个时候就要委托,如前面所讲事件发送器对事件接收器一无所知,通过委托作为一个中介,接收器把事件处理方法注册到事件中,这样就实现了由发送器->委托->接收器的过程了。
我们可以这样认为:委托是一个类,它定义了方法的类型,使得可以将方法当作另一个方法的参数来进行传递,这种将方法动态地赋给参数的做法,可以避免在程序中大量使用If-Else(Switch)语句,同时使得程序具有更好的可扩展性。
1.1.3 自定义委托
前面话有点难以理解,接下来我们通过具体的例子分析一下何谓委托,该如何实现委托。现在不是很喜欢搞多国语言化的吗?看看如何让我们的程序会说多种语言吧!
- /// <summary>
- /// the English speaker.
- /// </summary>
- /// <param name="name">The name.</param>
- public void EnglishSpeaker(string name)
- {
- Console.WriteLine(
- string.Format("Hello my name is {0} and I am English speaker.\n", name));
- }
- /// <summary>
- /// the Chineses speaker.
- /// </summary>
- public void ChineseSpeaker(string name)
- {
- Console.WriteLine(
- string.Format("您好我的名字叫{0},我是讲普通话的。\n", name));
- }
好啦现在我们有两个方法分别是说普通话和英语,现在我们的程序会说普通话和英语啦。现在我们考虑究竟什么时候讲普通话什么时候讲英语,那不简单我们加个判断就OK啦,是的我们可以通过switch或者if else就可以实现啦。
- /// <summary>
- /// 根据上下文调用不同的方法
- /// </summary>
- /// <param name="name">string</param>
- /// <param name="lang">enum</param>
- private static void Say(string name, Language lang)
- {
- switch (lang)
- {
- case Language.Chinese:
- Program.ChineseSpeaker(name);
- break;
- case Language.English:
- Program.EnglishSpeaker(name);
- break;
- default :
- break;
- }
- }
但假设我们现在又要增加新的语言西班牙语,同样我们可以增加西班牙语,但我们必须修改switch语句增加判断,这不符合OOP中的OCP(对扩展开放,对修改关闭原则),这时候委托该登场。
- /// <summary>
- /// Define speak delegate.
- /// </summary>
- /// <param name="name"></param>
- private delegate void SpeakDelegate(string name);
首先我们定义了一种委托类型SpeakDelegate,然后我们通过修改Say方法看看该如何使用委托变量。
- /// <summary>
- /// The base say function.
- /// </summary>
- /// <param name="name">The name.</param>
- /// <param name="speaker">The speaker.</param>
- private static void Say(string name, SpeakDelegate speaker)
- {
- ///Inoke the speaker function.
- speaker(name);
- }
现在我们的参数已经不是枚举类型了,而是一个委托类型变量,而且实现的具体代码也没有了switch语句了,比之前简单了许多。现在大家知道如何去调用Say方法吧!没错我们只需传递一个name和一个具体实现函数名就OK了。
- ///传递函数名进行委托方法绑定
- Program.Say("钧航", ChineseSpeaker);
- Program.Say("JK.Rush", EnglishSpeaker);
自定义委托相信大家都会了,接下来我将介绍一下.NET中委托实现,由于许多使用委托的例子都是事件,所以下面的例子也采用事件。但请大家要注意“可以使用委托,但却没有定义事件”的情况(例如:回调函数)。
1.1.4 .NET中的事件委托
举一个简单的例子,.NET中经常使用的控件Button,当我们把Button 控件 drap and drop到界面,然后双击界面的Button我们发现程序中自动生成了一个响应Button的事件方法,然后我们给事件方法添加Code之后,当我们点击该Button就响应该方法了,但我们没有看到代码中有任何的委托和事件之类的定义,其实这些.NET都已经做好了。我们可以查看如下文件。
图2事件委托实现
如上图所示我们打开Designer文件,事件委托的实现都在这里实现了。
其中,EventHandler就是一个代理类型,可以认为它是一个“类”,是所有返回类型为void,具备两个参数分别是object sender和EventArgs e,第一个参数表示引发事件的控件,或者说它表示点击的那个按钮。通过以下的代码我们细细解析一下。
- private void button1_Click(object sender, EventArgs e)
- {
- //获取被点击Button的实例
- Button objBotton = sender as Button;
- if (objBotton != null)
- {
- objBotton.Text = "Hello you click me.";
- objBotton.AutoSize = true;
- }
- else
- {
- //Exception Handle.
- }
- }
图3点击产生效果
OK现在明白了sender就是传递一个被点击对象的实例,第二个参数名叫e的EventArgs参数,用于 表示附加的事件关联的事件信息。当点击按钮时,没有附加任何关联的事件信息,如上的点击事件,第二参数并不表示任何有用的信息。但什么时候会用到呢?
我们先介绍一下EventArgs这个的类型。其实这个类并没有太多的功能,它主要是作为一个基类让其他类去实现具体的功能和定义,当我们搜索EventArgs发现很多类是继承于它的。
- public class EventArgs
- {
- // Fields
- public static readonly EventArgs Empty;
- // Methods
- static EventArgs();
- public EventArgs();
- }
举其中的ImageClickEventArgs为例,它继承于EventArgs,而且还添加了自己的字段用来基类X和Y的坐标值(这是一个ImageButton被点击时候响应的),然后获取该按钮的X和Y坐标。
- public sealed class ImageClickEventArgs : EventArgs
- {
- // Fields
- public int X;
- public int Y;
- // Methods
- public ImageClickEventArgs(int x, int y)
- {
- this.X = x;
- this.Y = y;
- }
- }
- //ImageButton点击响应时间
- protected void ibtnTest_Click(object sender, ImageClickEventArgs e)
- {
- this.lblCX.Text = e.X.ToString();
- this.lblCY.Text = e.Y.ToString();
- }
图4获取ImageClickEventArgs关联点击坐标
前面提到其他事件关联信息类型都是通过继承EventArgs实现的,所以说我们自己也可以自定义一个事件关联信息类型,如下我们只需继承EventArgs就OK了。
- /// <summary>
- /// 自定义事件关联类
- /// </summary>
- public class ColorChangedEventArgs : EventArgs
- {
- private Color color;
- /// <summary>
- /// Initializes a new instance of the <see cref="ColorChangedEventArgs"/> class.
- /// </summary>
- /// <param name="c">The c.</param>
- public ColorChangedEventArgs(Color c)
- {
- color = c;
- }
- /// <summary>
- /// Gets the color of the get.
- /// </summary>
- /// <value>
- /// The color of the get.
- /// </value>
- public Color GetColor
- {
- get { return color; }
- }
- }
1.1.5自定义事件委托
多播委托
前面使用的每个委托都只包含一个方法调用。调用一个委托就调用一个方法调用。如果要通过一个委托调用多个方法,那就需要使用委托的多播特性。如果调用多播委托,就可以按委托添加次序连续调用多个方法。为此,委托的签名就必须返回void;否则,就只能得到委托调用的最后一个方法的结果,接下来看看多播实现。
- namespace Multi_Delegate
- {
- delegate void StringProcessor();
- public class Person
- {
- private string _Name;
- public Person(string name)
- {
- this._Name = name;
- }
- public void Say()
- {
- Console.WriteLine("Hello my name is {0}, what's your name.\n", this._Name);
- }
- public void Reply()
- {
- Console.WriteLine("Hello my name is {0} and nice to meet you.\n", this._Name);
- }
- }
- class Program
- {
- static void Main(string[] args)
- {
- Person Jack = new Person("Jack");
- Person Oliver = new Person("Oliver");
- StringProcessor sp = null;
- //绑定多播方法调用
- sp += Jack.Say;
- sp += Oliver.Reply;
- sp();
- Console.ReadKey();
- }
- }
- }
也许有人觉得很简单,实现的确简单明了,就是通过“+”把方法调用绑定到委托变量中,如果我们用“-”就可以移除绑定到委托变量方法了。
事件
前面一直没有解释什么是事件,现在让我用一句话解释事件和委托的关系吧!
事件和委托关系就像是属性和字段的关系,为了刚好的实现OOP的编程原则,事件对委托进行了封装。
现在我们修改前面的代码,使用事件对委托进行封装。
- /// 使用事件对委托进行封装
- /// </summary>
- public class Say
- {
- /// <summary>
- /// 封装委托字段
- /// </summary>
- public static event SpeakDelegate speakDelegate;
- /// <summary>
- /// 调用委托具体实现方法
- /// </summary>
- /// <param name="name"></param>
- public static void SayManager(string name)
- {
- speakDelegate(name);
- }
- }
- /// <summary>
- /// 客户端调用委托
- /// </summary>
- /// <param name="args"></param>
- static void Main(string[] args)
- {
- Say.speakDelegate += Program.ChineseSpeaker;
- Say.speakDelegate += Program.EnglishSpeaker;
- Say.SayManager("Jackson");
- Console.ReadKey();
- }
图5自定义委托
现在让我们看看编译后Say类就可以充分证明我们的结论:事件是对委托封装。
图6自定义事件编译后的代码
大家看到在编译后的代码中出现了一个私有的委托变量,然后接下是一个公用事件委托变量,这进一步说明了事件是对委托的封装。
图7自定义事件编译后MSIL代码
1.1.6事件委托实现观察者模式
前面我们介绍按钮事件响应是从发送者和接收者的角度出发的,现在我们以设计模式中的观察者模式为例。
图8GoF观察者架构
- namespace GoFObserver
- {
- /// <summary>
- /// 充当Subject角色
- /// </summary>
- public class GofTelecom
- {
- public delegate void GofNews();
- public static event GofNews NewEvent;
- /// <summary>
- /// 发布通知方法
- /// </summary>
- /// <returns></returns>
- public static bool Notify()
- {
- if (NewEvent != null)
- {
- NewEvent();
- return false;
- }
- return true;
- }
- }
- public interface IObserver
- {
- void Update();
- }
- /// <summary>
- /// 观察者
- /// </summary>
- public class Programmer : IObserver
- {
- #region IObserver 成员
- public void Update()
- {
- Console.WriteLine("I am a greenhand programmer.\n");
- }
- #endregion
- }
- /// <summary>
- /// 观察者
- /// </summary>
- public class Architect : IObserver
- {
- #region IObserver 成员
- public void Update()
- {
- Console.WriteLine("OH...I am a top banana.\n");
- }
- #endregion
- }
- public class Program
- {
- static void Main(string[] args)
- {
- IList<IObserver> objObserver = new List<IObserver>();
- objObserver.Add(new Programmer());
- objObserver.Add(new Architect());
- foreach (IObserver ob in objObserver)
- {
- GofTelecom.NewEvent += ob.Update;
- }
- if (!GofTelecom.Notify())
- {
- Console.WriteLine("Notify successful.\n");
- }
- else
- {
- Console.WriteLine("Notify failed.\n");
- }
- Console.ReadKey();
- }
- }
- }
.NET 中的委托的更多相关文章
- C# 中的委托和事件
觉得这篇文章写的非常好,大神之作,由简入繁,对我这种初学者来说帮忙很大,特此留存下. 摘自:http://tracefact.net/CSharp-Programming/Delegates-and- ...
- Objective-C中的委托(代理)模式
我个人更喜欢把委托(Delegate)模式称为代理(Proxy)模式.还是那句话,第一次接触代理模式是在Java中接触的,在Java中实现代理模式和接口是少不了的.当时学习Spring的时候用到了接口 ...
- C# 中的委托和事件(转)
引言 委托 和 事件在 .Net Framework中的应用非常广泛,然而,较好地理解委托和事件对很多接触C#时间不长的人来说并不容易.它们就像是一道槛儿,过了这个槛的人,觉得真是太容易了,而没有过去 ...
- C# 中的委托和事件(转载)
引言 委托 和 事件在 .Net Framework中的应用非常广泛,然而,较好地理解委托和事件对很多接触C#时间不长的人来说并不容易.它们就像是一道槛儿,过了这个槛的人,觉得真是太容易了,而没有过去 ...
- 【转】C# 中的委托和事件
阅读目录 C# 中的委托和事件 引言 将方法作为方法的参数 将方法绑定到委托 事件的由来 事件和委托的编译代码 委托.事件与Observer设计模式 .Net Framework中的委托与事件 总结 ...
- 第3章 C#中的委托和事件
.NET框架中的委托和事件 using System; using System.Collections.Generic; using System.Linq; using System.Text; ...
- 分分钟用上C#中的委托和事件之窗体篇
上次以鸿门宴的例子写了一篇名为<分分钟用上C#中的委托和事件>的博文,旨在帮助C#初学者迈过委托和事件这道坎,能够用最快的速度掌握如何使用它们.如果觉得意犹未尽,或者仍然不知如何在实际应用 ...
- 《C#高级编程》学习笔记------C#中的委托和事件(续)
本文转载自张子阳 目录 为什么要使用事件而不是委托变量? 为什么委托定义的返回值通常都为void? 如何让事件只允许一个客户订阅?(事件访问器) 获得多个返回值与异常处理 委托中订阅者方法超时的处理 ...
- c#中的委托和事件(转)
引言 委托 和 事件在 .Net Framework中的应用非常广泛,然而,较好地理解委托和事件对很多接触C#时间不长的人来说并不容易.它们就像是一道槛儿,过了这个槛的人,觉得真是太容易了,而没有过去 ...
- C#中的委托和事件(续)
转自张子阳的博客http://www.tracefact.net/CSharp-Programming/Delegates-and-Events-Advanced.aspx 引言 如果你看过了 C#中 ...
随机推荐
- new在c#方法中的使用
new在c#中有三种用法: 1.实例化对象 2.泛型约束 3.用在方法前.new和override的区别在于:override用于重写父类的方法:new用于隐藏方法,它调用的方法来自于申明的类,如果申 ...
- Linux下java获取CPU、内存、磁盘IO、网络带宽使用率
一.CPU 使用proc文件系统,"proc文件系统是一个伪文件系统,它只存在内存当中,而不占用外存空间.它以文件系统的方式为访问系统内核数据的操作提供接口.用户和应用程序可以通过proc得 ...
- 验证标题是否存在(TextBox控件失去焦点验证)
首先解释两个属性, AutoPostBack 属性用于设置或返回当用户在 TextBox 控件中按 Enter 或 Tab 键时,是否发生自动回传到服务器的操作. 如果把该属性设置为 TRUE,则启用 ...
- imread() not working in OpenCV 2.4.11 Debug mode
The OpenCV function imread() not working in OpenCV 2.4.11 Debug mode of VS2010 under Win32, the way ...
- [FollowUp] Combinations 组合项
这是Combinations 组合项 的延伸,在这里,我们允许不同的顺序出现,那么新的题目要求如下: Given two integers n and k, return all possible c ...
- ACM对时间掌控力和日积月累的习惯的意义
马云说,要想创业成功,不是要知道现在什么东西最火,而是要清楚的知道十年以后什么东西最火.这就意味着,你对时间掌控力,至少要有十年. 但是仔细回想一下自己的学生时代,自己对时间的把握是怎样的?有些人只能 ...
- 安装Bind过程中提示丢失MSVCR110.dll的解决办法
前几天在线安装Visual Studio 2012 Update 3,由于在线安装需要不断下载安装文件,时间很长,后来等不下去,就取消了,不幸的是VS启动不了了,弹出“devenv.exe – 系统错 ...
- POJ 1088 滑雪(记忆化搜索)
滑雪 Time Limit: 1000MS Memory Limit: 65536K Total Submissions: 92384 Accepted: 34948 Description ...
- UITableview xib里面 cell 按钮的回调
// MoreBtnCell.m#import <UIKit/UIKit.h> @interface MoreBtnCell : UITableViewCell @property (w ...
- Win10开始菜单打不开?两个办法可破
很多人在安装Windows10后,都遇到过开始菜 单无法打开和Cortana框架无法输入文字的问题, 这种问题在系统更新后特别频繁.Windows会报错 为关键错误,并提示在下次登录会进行解决,同时要 ...