委托(delegate)特别用于实现事件和回调方法。所有的委托(Delegate)都派生自 System.Delegate 类。事件是一种特殊的多播委托,仅可以从声明事件的类或结构中对其进行调用。类或对象可以通过事件向其他类或对象通知发生的相关事情。本文主要介绍.NET(C#) 中委托和事件的使用总结。

1、委托的简单使用

一个委托类型定义了该类型的实例能调用的一类方法,这些方法含有同样的返回类型和同样参数(类型和个数相同)。委托和接口一样,可以定义在类的外部。

例如,

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

namespace ConsoleApplication
{
    delegate int Calculator(int x);
    class Program
    {
        static int Double(int x) { return x * 2; }
        static void Main(string[] args)
        {
            Calculator c = Double;
            int result = c(2);
            Console.Write(result);
            Console.ReadKey();
        }
    }
}

2、用委托实现插件式编程

委托是一个能把方法作为参数传递的对象,通过这个可以来实现插件式编程。

例如,

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

namespace ConsoleApplication
{
    delegate int Calculator(int x);
    class Program
    {
        static int Double(int x) { return x * 2; }
        static void Main(string[] args)
        {
            int[] values = { 1, 2, 3, 4 };
            Utility.Calculate(values, Double);//使用方法
            Utility.Calculate(values, x => x * 2);//使用Lambda表达式

            foreach (int i in values)
                Console.Write(i + " "); // 2 4 6 8
            Console.ReadKey();
        }
    }
    class Utility
    {
        public static void Calculate(int[] values, Calculator c)
        {
            for (int i = 0; i < values.Length; i++)
                values[i] = c(values[i]);
        }
    }
}

3、多播委托

多播委托是指在一个委托中注册多个方法,在注册方法时可以在委托中使用加号运算符或者减号运算符来实现添加或撤销方法。创建一个委托被调用时要调用的方法的调用列表。这被称为委托的 多播(multicasting),也叫组播。

例如,

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

namespace ConsoleApplication
{
    public delegate void ProgressReporter(int percentComplete);
    public class Utility
    {
        public static void Match(ProgressReporter p)
        {
            if (p != null)
            {
                for (int i = 0; i <= 10; i++)
                {
                    p(i * 10);
                    System.Threading.Thread.Sleep(100);
                }
            }
        }
    }
    class Program
    {
        static void Main(string[] args)
        {
            ProgressReporter p = WriteProgressToConsole;
            p += WriteProgressToFile;
            Utility.Match(p);
            Console.WriteLine("Done.");
            Console.ReadKey();
        }
        static void WriteProgressToConsole(int percentComplete)
        {
            Console.WriteLine(percentComplete + "%");
        }
        static void WriteProgressToFile(int percentComplete)
        {
            System.IO.File.AppendAllText("progress.txt", percentComplete + "%");
        }
    }
}

4、静态方法和实例方法对于委托的区别

一个类的实例的方法被赋给一个委托对象时,在上下文中不仅要维护这个方法,还要维护这个方法所在的实例。System.Delegate 类的Target属性指向的就是这个实例。

例如,

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

namespace BRG
{
    public delegate void ProgressReporter(int percentComplete);
    class Program
    {
        static void Main(string[] args)
        {
            X x = new X();
            ProgressReporter p = x.InstanceProgress;
            p(1);
            Console.WriteLine(p.Target == x); // True
            Console.WriteLine(p.Method); // Void InstanceProgress(Int32)    
        }
        static void WriteProgressToConsole(int percentComplete)
        {
            Console.WriteLine(percentComplete + "%");
        }
        static void WriteProgressToFile(int percentComplete)
        {
            System.IO.File.AppendAllText("progress.txt", percentComplete + "%");
        }
    }
    class X
    {
        public void InstanceProgress(int percentComplete)
        {
            // do something    
        }
    }
}

但对于静态方法,System.Delegate 类的Target属性是Null,所以将静态方法赋值给委托时性能更好。

5、泛型委托

泛型委托和泛型的用法一样,就是含有泛型参数的委托。

例如,

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

namespace ConsoleApplication
{
    public delegate T Calculator<T>(T arg);
    class Program
    {
        static int Double(int x) { return x * 2; }
        static void Main(string[] args)
        {
            int[] values = { 1, 2, 3, 4 };
            Utility.Calculate(values, Double);
            foreach (int i in values)
                Console.Write(i + " "); // 2 4 6 8
            Console.ReadKey();
        }
    }
    class Utility
    {
        public static void Calculate<T>(T[] values, Calculator<T> c)
        {
            for (int i = 0; i < values.Length; i++)
                values[i] = c(values[i]);
        }
    }
}

6、事件的基本使用

声明一个事件很简单,只需在声明一个委托对象时加上event关键字就行。

例如,

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace Example_EventTest
{
    class Judgment
    {
        //定义一个委托
        public delegate void delegateRun();
        //定义一个事件
        public event delegateRun eventRun;
        //引发事件的方法
        public void Begin()
        {
            eventRun();//被引发的事件
        }
    }
    class RunSports
    {
        //定义事件处理方法
        public void Run()
        {
            Console.WriteLine("开始运行方法");
        }
    }
    class Program
    {
        static void Main(string[] args)
        {
            RunSports runsport = new RunSports();//实例化事件发布者
            Judgment judgment = new Judgment();//实例化事件订阅者
            //订阅事件
            judgment.eventRun+=new Judgment.delegateRun(runsport.Run);
            //引发事件
            judgment.Begin();
            Console.ReadKey();
        }
    }
}

注意:事件有一系列规则和约束用以保证程序的安全可控,事件只有 +=-= 操作,这样订阅者只能有订阅或取消订阅操作,没有权限执行其它操作。如果是委托,那么订阅者就可以使用 = 来对委托对象重新赋值(其它订阅者全部被取消订阅),甚至将其设置为null,甚至订阅者还可以直接调用委托。

事件保证了程序的安全性和健壮性。

7、事件的标准模式

.NET 框架为事件编程定义了一个标准模式。设定这个标准是为了让.NET框架和用户代码保持一致。System.EventArgs是标准模式的核心,它是一个没有任何成员,用于传递事件参数的基类。按照标准模式,事件定义委托,必须满足以下条件:

1)必须是 void 返回类型;

2)必须有两个参数,且第一个是object类型,第二个是EventArgs类型(的子类);

3)它的名称必须以EventHandler结尾。

例如,

using System;
namespace ConsoleApplication1
{
    class Program
    {
        static void Main(string[] args)
        {
            Counter c = new Counter(new Random().Next(10));
            c.ThresholdReached += c_ThresholdReached;
            Console.WriteLine("press 'a' key to increase total");
            Console.WriteLine("adding one");
            c.Add(1);
            while (Console.ReadKey(true).KeyChar == 'a')
            {
                Console.WriteLine("adding one");
                c.Add(1);
            }
        }
        static void c_ThresholdReached(object sender, ThresholdReachedEventArgs e)
        {
            Console.WriteLine("The threshold of {0} was reached at {1}.", e.Threshold,  e.TimeReached);
            Environment.Exit(0);
        }
    }
    class Counter
    {
        private int threshold;
        private int total;
        public Counter(int passedThreshold)
        {
            threshold = passedThreshold;
        }
        public void Add(int x)
        {
            total += x;
            if (total >= threshold)
            {
                ThresholdReachedEventArgs args = new ThresholdReachedEventArgs();
                args.Threshold = threshold;
                args.TimeReached = DateTime.Now;
                OnThresholdReached(args);
            }
        }
        protected virtual void OnThresholdReached(ThresholdReachedEventArgs e)
        {
            EventHandler<ThresholdReachedEventArgs> handler = ThresholdReached;
            if (handler != null)
            {
                handler(this, e);
            }
        }
        public event EventHandler<ThresholdReachedEventArgs> ThresholdReached;
    }
    public class ThresholdReachedEventArgs : EventArgs
    {
        public int Threshold { get; set; }
        public DateTime TimeReached { get; set; }
    }
}

推荐文档