介绍

委托和事件是任何 Windows 或 Web 应用程序的基础,它们允许开发者“订阅”用户执行的特定操作。因此,开发者不需要期待所有的事件并过滤出想要的部分,而是选择想要接收通知并对这些操作作出反应。

委托 是一种指示 C# 在事件触发时调用哪个方法的方式。例如,当你在表单上点击按钮时,程序会调用一个特定的方法。委托就是这种指针。委托的优点是你可以通知多个方法某个事件已经发生,若你希望如此。

事件 是 .NET 框架提供的通知,表示某个操作已经发生。每个事件包含有关特定事件的信息,例如,鼠标点击事件会告诉你点击了哪个鼠标按钮以及点击的位置。

假设你编写了一个仅对按钮点击作出反应的程序,以下是事件发生的顺序:

  1. 用户按下鼠标按钮并点击一个按钮
  2. .NET 框架触发 MouseDown 事件
  3. 用户释放鼠标按钮
  4. .NET 框架触发 MouseUp 事件
  5. .NET 框架触发 MouseClick 事件
  6. .NET 框架触发按钮上的 Clicked 事件

由于按钮的点击事件已经被订阅,程序会忽略其余的事件,而你的委托会告知 .NET 框架在事件发生时调用哪个方法。

委托

委托是 C# 事件处理的基础。它们是一个抽象构造,用于创建可以引用方法并调用这些方法的对象。委托声明指定了一个特定的方法签名。可以将一个或多个方法的引用添加到委托实例中,然后调用该委托实例,这会依次调用所有添加到委托中的方法。一个简单的例子:

using System;
delegate void Procedure();

class DelegateDemo
{
    public static void Method1()
    {
        Console.WriteLine("Method 1");
    }

    public static void Method2()
    {
        Console.WriteLine("Method 2");
    }

    public void Method3()
    {
        Console.WriteLine("Method 3");
    }

    static void Main()
    {
        Procedure someProcs = null;

        someProcs += new Procedure(DelegateDemo.Method1);
        someProcs += new Procedure(Method2);  // Example with omitted class name

        DelegateDemo demo = new DelegateDemo();

        someProcs += new Procedure(demo.Method3);
        someProcs();
    }
}

在这个例子中,委托通过 delegate void Procedure() 声明。这条声明本身并不执行任何工作,而是声明了一个名为 Procedure 的委托类型,该委托不接受参数且没有返回值。接着,在 Main() 方法中,语句 Procedure someProcs = null; 实例化了一个委托。该委托最初并没有引用任何方法。接下来的语句 someProcs += new Procedure(DelegateDemo.Method1)someProcs += new Procedure(Method2) 向委托实例中添加了两个静态方法。注意,类名也可以省略,因为语句发生在 DelegateDemo 类中。语句 someProcs += new Procedure(demo.Method3) 将一个非静态方法添加到委托实例中。对于非静态方法,方法名前面需要加上对象引用。当委托实例被调用时,Method3() 会在被添加到委托实例时提供的对象上执行。最后,语句 someProcs() 调用委托实例,所有已添加到委托中的方法将按添加顺序依次被调用。

委托实例中已添加的方法可以使用 -= 操作符移除:

someProcs -= new Procedure(DelegateDemo.Method1);

在 C# 2.0 中,向委托实例添加或移除方法引用可以简化为:

someProcs += DelegateDemo.Method1;
someProcs -= DelegateDemo.Method1;

如果委托实例当前没有任何方法引用,调用该委托实例将导致 NullReferenceException

需要注意的是,如果委托声明指定了返回类型并且多个方法被添加到委托实例中,那么调用该委托实例将返回最后一个引用方法的返回值。其他方法的返回值无法获取(除非明确地存储在其他地方)。

匿名委托

匿名委托是写委托代码的简洁方式,使用 delegate 关键字来指定。委托代码还可以引用声明它们的函数的局部变量。匿名委托会自动被编译器转换成方法。例如:

using System;
delegate void Procedure();

class DelegateDemo2
{
    static Procedure someProcs = null;

    private static void AddProc()
    {
        int variable = 100;
 
        someProcs += new Procedure(delegate
            {
                Console.WriteLine(variable);
            });
    }

    static void Main()
    {
        someProcs += new Procedure(delegate { Console.WriteLine("test"); });
        AddProc();
        someProcs();
        Console.ReadKey();
    }
}

它们可以像普通方法一样接受参数:

using System;
delegate void Procedure(string text);

class DelegateDemo3
{
    static Procedure someProcs = null;
    
    private static void AddProc()
    {
        int variable = 100;
 
        someProcs += new Procedure(delegate(string text)
            {
                Console.WriteLine(text + ", " + variable.ToString());
            });
    }
    
    static void Main()
    {
        someProcs += new Procedure(delegate(string text) { Console.WriteLine(text); });
        AddProc();
        someProcs("testing");
        Console.ReadKey();
    }
}

输出为:

testing
testing, 100

Lambda 表达式

Lambda 表达式是实现匿名委托的更清晰方式。其形式为:

(type1 arg1, type2 arg2, ...) => expression

这等价于:

delegate(type1 arg1, type2 arg2, ...)
{
    return expression;
}

如果只有一个参数,可以省略括号。类型名称也可以省略,让编译器从上下文中推断类型。在以下示例中,str 是一个字符串,返回类型是 int

Func<string, int> myFunc = str => int.Parse(str);

这等价于:

Func<string, int> myFunc = delegate(string str)
{
    return int.Parse(str);
};

事件

事件是委托的特殊形式,用于促进事件驱动编程。事件是类成员,不能在类外部被调用,无论其访问修饰符是什么。因此,尽管一个公开声明的事件允许其他类使用 +=-= 操作符,但触发事件(即调用委托)仅在包含事件的类内被允许。一个简单的例子:

public delegate void ButtonClickedHandler();
class Button
{
    public event ButtonClickedHandler ButtonClicked;
    ButtonClicked += ()=>{Console.WriteLine("click simulation !");};    
    
    public void SimulateClick()
    {
        if (ButtonClicked != null)
        {
            ButtonClicked();
        }
    }
    ...
}

其他类中的方法可以通过将其方法添加到事件委托中来订阅该事件:

Button b = new Button();
b.ButtonClicked += ButtonClickHandler;

private void ButtonClickHandler()
{
    // 处理事件
}

尽管事件被声明为公开,事件不能直接在类外触发,只能在包含它的类中触发。

最后修改: 2025年01月11日 星期六 23:43