图形化用户界面(GUI)中,当我们单击一个按钮时就会有一个响应操作,这就是激活了按钮的单击(click)事件(event);但是,在创建按钮组件时并不能决定单击按钮后会有什么操作,比如“确定”和“取消”就是完全不同的操作,此时,只能将按钮单击事件的响应代码交给使用按钮的开发者来编写。

在软件开发中,还会许多类似的情形出现,比如,某个组件需要特定的操作,但在开发时无法确认具体的实现,或者使用者可以自定义操作;此时,就可以通过一种机制,让组件的使用者来编写实现代码。在C#中,可以使用委托(delegate)来处理这种情况,而事件则是委托的一种应用形式。

下面的示例,假设在软件运行过程中需要做一些记录,以便能够更有效地分析软件使用过程中的问题,但是,开发日志记录组件时并不确定使用什么形式保存数据,如使用文件、数据库等,此时,就可以使用委托来解决。

下面的代码,我们定义了一个名为DLogger的委托类型。

namespace ConsoleTest
{
    public delegate void DLogger(string msg);
}

代码中可以看到,除了delegate关键字,其他的内容与方法的定义非常相似,没错,因为很多情况下,委托就是通过方法完成具体的工作。

下面的代码,我们创建了CLog类,用于完成日志的记录工作。

public static class CLog
{
    public static DLogger Logger = new DLogger(DefaultLogger);
    // 默认的处理方法
    private static void DefaultLogger(string msg)
    {
        Console.WriteLine("默认操作");
        Console.WriteLine(msg);
    }
    // 日志记录方法
    public static void Log(string msg)
    {
        Logger(msg);
    }
    //
}

在一个项目中,日志记录操作的方式应该是一致的,所以,我们将CLog类定义为静态类,其成员也都是静态的,其中:

  • Logger对象定义为DLogger委托类型,并初始化为调用DefaultLogger()方法。
  • DefaultLogger()方法,用于默认的日志记录操作。
  • Log()方法,提供给使用者调用的日志记录方法。

下面的代码,我们试用一下CLog.Log()方法的默认执行效果。

static void Main(string[] args)
{
        CLog.Log("Hello Delegate");
}

代码执行结果如下图所示。

enter image description here

那么,我们如何修改CLog.Log()方法的操作呢?下面的代码就可以做到。

static void Main(string[] args)
{
        // 修改日志委托的实现
        CLog.Logger = new DLogger(WriteLog);
        //
        CLog.Log("Hello Delegate");
}
//
static void WriteLog(string msg)
{
        Console.WriteLine("日志写入数据库");
        Console.WriteLine(msg);
}

代码中,我们定义了一个WriteLog()方法,请注意它的返回值和参数设置应与DLogger委托类型保持一致;然后,在Main()方法中,将CLog.Logger重新设置为调用WriteLog()方法;执行结果如下图所示。

enter image description here

示例中,大家可以看到,WriteLog()方法在整个软件中只需要调用一次即可,此时单独定义一个方法显得有些多余;在这种情况下,我们可以使用Lambda表达式来完成相同的任务,如下面的代码。

static void Main(string[] args)
{
        //
        CLog.Logger = (string msg) =>
        {
            Console.WriteLine("日志写入数据库");
            Console.WriteLine(msg);
        };
        //
        CLog.Log("Hello Delegate");
}

这里,使用Lambda表达式重新设置了CLog.Logger委托对象的操作,此处使用=>符号,这是Lambda表达式的标志,而Lambda表达式定义的格式如下。

(<参数列表>) => {
   <语句块>
};

我们可以看到,Lambda表达式的定义非常像一个简化的方法定义,只是没有了方法名,而且不需要指定返回值类型;实际上,Lambda表达式的是可以返回数据的,只需要在<语句块>中使用return语句返回数据即可,就像方法的实现一样。如下面的代码。

// 加法运算委托
delegate int DSum(int x, int y);
//
static void Main(string[] args)
{
        //
        DSum sum = (int num1, int num2) =>
        {
            return num1 + num2;
        };
        //
        int x = 10;
        int y = 99;
        Console.WriteLine("{0}+{1}={2}", x, y, sum(x, y));
}

本例中,我们定义了DSum委托类型,它包括两个int类型的参数,其返回值同样是int类型。Main()方法中,我们使用Lambda表达式定义了sum对象(DSum委托类型)的实现,其中将返回两个参数相加的和。代码执行结果如下图所示。

enter image description here

以上,我们看到了如何使用方法和Lambda表达式来实现委托对象的具体操作,在实际应用中,如果委托类型没有返回值,我们还可以使用一个委托对象同时完成多个实现,如下面的代码。

static void Main(string[] args)
{
        CLog.Logger = new DLogger(WriteLog);
        CLog.Logger += new DLogger(SendLog);
        //
        CLog.Log("多路广播委托");
}
//
static void WriteLog(string msg)
{
        Console.WriteLine("日志写入数据库");
        Console.WriteLine(msg);
}
//
static void SendLog(string msg)
{
        Console.WriteLine("日志发送到远程服务器");
        Console.WriteLine(msg);
}

本例,我们定义了两个方法,分别是WriteLog()和SendLog()方法,它们的参数和DLogger委托类型定义的相同的,并且没有返回值。Main()方法中,我们通过=和+=运算符将两个方法的实现都添加到了CLog.Logger委托对象中,当调用CLog.Log()方法时,就会调用WriteLog()和SendLog()两个方法,执行结果如下图所示。

enter image description here

如果大家设计软件的图形界面就会发现,控件(如按钮)的事件的响应方法就是这样进行关联的,而这种操作就叫做多路广播委托(multicast delegate),简称多播委托。

CHY软件小屋原创作品!