C#委托

委托和事件在.NET Framwork中的应用非常广泛。

在初步了解C#的注册委托机制后,认为其作用类似于C++的函数指针。当我们需要在一个函数中将另一个函数作为参数时我们就需要委托机制的帮助。

举例有委托的定义:

public delegate void GreetingDelegate(string name);

编写GreetPeople()方法

        public void GreetingPeople(string name,GreetingDelegate MakeGreeting)
        {
            MakeGreeting(name);
        }

 实际上,委托在编译的时候确实会编译成类。因为Delegate是一个类,所以在任何可以申明类的地方都可以申明委托。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace 委托_Test
{
    public delegate void GreetingDelegate(string name);
    class Program
    {
       private static void EnglishGreeting(string name)
        {
            Console.WriteLine("Morning," + name);
        }

        private static void ChineseGreeting(string name)
       {
           Console.WriteLine("早上好," + name);
       }
        private static void GreetPeople(string name,GreetingDelegate MakeGreeting)
       {
           MakeGreeting(name);
       }
        static void Main(string[] args)
        {
            GreetingDelegate delegate1;
            delegate1 = ChineseGreeting;
            //给委托绑定一个方法
            delegate1 += EnglishGreeting;
            //绕过GreetPeople方法
            delegate1("李华");
            //给委托取消绑定
            delegate1 -= EnglishGreeting;
            GreetPeople("李华", delegate1);
            string name = "Jimmy";
            GreetPeople(name, EnglishGreeting);
            Console.ReadKey();
        }
    }
}


 委托是一个类,它定义了方法的类型,使得可以将方法当作另一个方法的参数来进行传递,将这种方法动态的赋给参数的做法,可以避免在程序中大量使用if-else(switch)语句,同时使得程序有更好的扩展性。

                GreetingDelegate delegate1;
            delegate1 = ChineseGreeting;
            delegate1("李华");

 我们利用上述代码,我们为委托变量绑定了一个ChineseGreeting方法并绕过了对GreetPeople的调用。

            GreetingDelegate delegate1;
            delegate1 = ChineseGreeting;
            //给委托绑定一个方法
            delegate1 += EnglishGreeting;

 上述代码为委托绑定了两个方法。

            //给委托取消绑定
            delegate1 -= EnglishGreeting;

 给委托取消绑定。

            string name = "Jimmy";
            GreetPeople(name, EnglishGreeting);

 最后输出结果为

早上好,李华

Morning,李华

早上好,李华

Morning,Jimmy

在实际应用中通常都是GreetPeople在一个类中,ChineseGreeting和EnglishGreeting在另外的类中。

假设我们将GreetingPeople()放在一个叫做GreetingManager的类中,那么新程序该是这样的的。类GreetingManager

namespace 委托_Test2
{
    //定义委托,它定义了可以代表的方法的类型
    public delegate void GreetingDelegate(string name);
    class GreetingManager
    {
        //注意此方法,它接受一个GreetingDelegate类型的参数,该参数是返回值为空,参数为string类型的方法
        public void GreetingPeople(string name,GreetingDelegate MakeGreeting)
        {
            MakeGreeting(name);
        }
    }
}

  Program.cs

namespace 委托_Test2
{
    class Program
    {
        private static void EnglishGreeting(string name)
        {
            Console.WriteLine("Morning," + name);
        }

        private static void ChineseGreeting(string name)
        {
            Console.WriteLine("早上好," + name);
        }
        static void Main(string[] args)
        {
            //绕过GreetingPeople方法,通过委托来直接调用EnglishGreeting
            GreetingDelegate delegate1;
            delegate1 = EnglishGreeting;
            delegate1("Jimmy");

            GreetingManager gm = new GreetingManager();
            gm.GreetingPeople("李华", ChineseGreeting);

        }
    }
}

  面向对象的设计,讲究的是对象的封装,我们可以将委托变量封装到GreetManager类中。可以做如下改写

namespace 委托_Test2
{
    //定义委托,它定义了可以代表的方法的类型
    public delegate void GreetingDelegate(string name);
    class GreetingManager
    {
        public GreetingDelegate delegate2;
        //注意此方法,它接受一个GreetingDelegate类型的参数,该参数是返回值为空,参数为string类型的方法
        public void GreetingPeople(string name,GreetingDelegate MakeGreeting)
        {
            MakeGreeting(name);
        }
        public void GreetingPeople(string name)
        {
            //在GreetingManger类的内部声明delegate2变量
            if(delegate2 != null)//如果有方法注册委托变量
            {
                delegate2(name);//通过委托调用方法
            }
        }
    }
}

  我们可以这样使用委托变量:

        static void Main(string[] args)
        {

            GreetingManager gm = new GreetingManager();
            gm.GreetingPeople("李华", ChineseGreeting);

            gm.delegate2 = ChineseGreeting;
            gm.delegate2 += EnglishGreeting;

            gm.GreetingPeople("李华", gm.delegate2);
            gm.delegate2("李华");

            Console.ReadKey();
        }

  尽管这样使用可以达到我们的效果,但是还是存在问题。

  在这里,delegate1和我们平时用的string类型的变量没有什么分别,而我们知道,并不是所有的字段都应该声明成public,合适的做法是应该public的时候public,应该private的时候private。
  我们先看看如果把 delegate1 声明为 private会怎样?结果就是:这简直就是在搞笑。因为声明委托的目的就是为了把它暴露在类的客户端进行方法的注册,你把它声明为private了,客户端对它根本就不可见,那它还有什么用?
再看看把delegate1 声明为 public 会怎样?结果就是:在客户端可以对它进行随意的赋值等操作,严重破坏对象的封装性。
最后,第一个方法注册用“=”,是赋值语法,因为要进行实例化,第二个方法注册则用的是“+=”。但是,不管是赋值还是注册,都是将方法绑定到委托上,除了调用时先后顺序不同,再没有任何的分别,这样不是让人觉得很别扭么?
  我们想想,如果delegate1不是一个委托类型,而是一个string类型,你会怎么做?答案是使用属性对字段进行封装。
于是,Event出场了,它封装了委托类型的变量,使得:在类的内部,不管你声明它是public还是protected,它总是private的。在类的外部,注册“+=”和注销“-=”的访问限制符与你在声明事件时使用的访问符相同。
 
以下是一个示例代码 Heater.cs
namespace 委托_Test3
{
    class Heater
    {
        private int temperature;
        public delegate void BoilHander(int param);
        public event BoilHander BoilEvent;

        public void BoilWater()
        {
            for(int i = 0; i<=100;i++)
            {
                temperature = i;
                if(temperature > 95)
                {
                    if(BoilEvent != null)
                    {
                        BoilEvent(temperature);
                    }
                }
            }
        }
    }
}

  Program.cs

namespace 委托_Test3
{
    public class Alarm
    {
        public void MakeAlert(int param)
        {
            Console.WriteLine("Alarm: 水已经{0}度了", param);
        }
    }

    public class Display
    {
        public static void ShowMsg(int param)
        {
            Console.WriteLine("Display:水快烧开了,当前温度{0}度", param);
        }
    }

    public class Display2
    {
        public static void ShowMsg(Object sender,Heater2.BoiledEventArgs e)
        {
            Heater2 heater2 = (Heater2)sender;
            Console.WriteLine("Display:{0} - {1}", heater2.area, heater2.type);
            Console.WriteLine("Dispaly:水快烧开了,当前温度:{0}度", e.temperture);
            Console.WriteLine();
        }
    }
    public class Alarm2 
    {
        public void MakeAlert(Object sender,Heater2.BoiledEventArgs e)
        {
            Heater2 heater2 = (Heater2)sender;
            Console.WriteLine("Alarm:{0} - {1}:", heater2.area, heater2.type);
            Console.WriteLine("Alarm::嘀嘀嘀,水已经{0}度了", e.temperture);
            Console.WriteLine();
        }
    }
    class Program
    {
        static void Main(string[] args)
        {
            Heater heater = new Heater();
            Alarm alarm = new Alarm();

            heater.BoilEvent += alarm.MakeAlert;
            heater.BoilEvent += (new Alarm()).MakeAlert;
            heater.BoilEvent += Display.ShowMsg;
            heater.BoilWater();
            Console.ReadKey();

        }
    }
}

  下面介绍对Observer设计模式做一个了解。

  Observer设计模式是为了定义对象间的一种一对多的依赖关系,以便于当一个对象的状态改变时,其他依赖于它的对象会被自动告知并更新。Observer模式是一种松耦合的设计模式。

  委托声明原型中的Object类型的参数代表了Subject,也就是被监视对象,在本例中是 Heater(热水器)。回调函数(比如Alarm的MakeAlert)可以通过它访问触发事件的对象(Heater)。 EventArgs 对象包含了Observer所感兴趣的数据,在本例中是temperature。上面这些其实不仅仅是为了编码规范而已,这样也使得程序有更大的灵活性。

如下是修改后的代码

Heater2.cs

namespace 委托_Test3
{
    public class Heater2
    {
        private int temperature;
        public string type = "RealFire 001";
        public string area = "China Si`chuan";

        //Object类型代表的了Subject也就是被监视对象,EventArgs对象包含了Observer所感兴趣的数据
        public delegate void BoiledEventHandler(Object sender, BoiledEventArgs e);
        public event BoiledEventHandler Boiled;

        public class BoiledEventArgs:EventArgs
        {
            public readonly int temperture;
            public BoiledEventArgs(int temperture)
            {
                this.temperture = temperture;
            }
        }

        protected virtual void OnBoiled(BoiledEventArgs e)
        {
            if(Boiled != null)
            {
                Boiled(this, e);
            }
        }

        public void BoilWater()
        {
            for(int i = 0; i<=100;i++)
            {
                temperature = i;
                if(temperature > 95)
                {
                    BoiledEventArgs e = new BoiledEventArgs(temperature);
                    OnBoiled(e);
                }
            }
        }
    }
}

  Program.cs

 class Program
    {
        static void Main(string[] args)
        {
            Heater2 heater2 = new Heater2();
            Alarm2 alarm2 = new Alarm2();
            heater2.Boiled += alarm2.MakeAlert;
            heater2.Boiled += (new Alarm2()).MakeAlert;
            heater2.Boiled += Display2.ShowMsg;
            heater2.BoilWater();
            Console.ReadKey();
        }
}