zoukankan      html  css  js  c++  java
  • 一起谈.NET技术,.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();
    }
    }
    }
  • 相关阅读:
    hdu5728 PowMod
    CF1156E Special Segments of Permutation
    CF1182E Product Oriented Recurrence
    CF1082E Increasing Frequency
    CF623B Array GCD
    CF1168B Good Triple
    CF1175E Minimal Segment Cover
    php 正则
    windows 下安装composer
    windows apache "The requested operation has failed" 启动失败
  • 原文地址:https://www.cnblogs.com/waw/p/2162872.html
Copyright © 2011-2022 走看看