C Sharp/Delegates and Events

维基教科书,自由的教学读本

Template:C sharp/Navigation

介绍[编辑]

委托(delegate)与事件(event)是Windows或Web应用程序的基础。委托基本上相当于C语言的函数指针;委托数据告诉C#一个事件被触发时哪些方法被调用。事件是.NET framework通知某个动作发生了;事件包含了特定信息,如鼠标点击事件包含了哪个鼠标按键在窗体的哪里被点击。

委托[编辑]

委托是一个结构,可引用一个方法,该方法以后可被调用。一个委托的声明指示特定的方法签名。一个或更多个方法的引用可以增添到一个委托实例上。委托实例可以被调用,实际上是该委托实例引用的所有方法按照加入该委托的次序被调用。一个简单例子:

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为一个无参、返回为空的委托类型。

已被委托引用的方法可以删除引用:

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;
}

如果只有一个参数,则圆括号可省略。参数类型也可以省略,让编译去去推导。例如:

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

这等效于:

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

事件[编辑]

事件是一种特殊的委托,用于事件驱动编程。事件可以是类从成员,但即使其访问指示符是public,事件只能在包含它的类中被调用,但在其它类可以为事件增加或减少方法引用。例如:

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()
{
    //Handle the event
}

即使这个事件被声明为public,它之恶能在包含它的类中被直接fire。