第1页:C#的代理和事件 第2页:C#的代理和事件
多播 (Multicasting)
虽然指向成员函数的功能已让人感到满意,但利用代理,您还可以巧妙地完成其它一些任务。在 C# 中,代理是“多播”的,这表示它们可同时指向一个以上的函数(即基于
System.MulticastDelegate
类型)。多播代理将维护一个函数列表。当调用该代理时,将会调用列表中的所有函数。我们可以添加第一个示例中的记录函数,然后调用这两个代理。要将两个代理组合起来,可使用
Delegate.Combine() 函数。其代码如下:
MyClass.LogHandler lh = (MyClass.LogHandler)
Delegate.Combine(new Delegate[]
{new MyClass.LogHandler(Logger),
new MyClass.LogHandler(fl.Logger)});
啊呀,真的是很难看!幸好 C# 提供了一种更好的语法,而不用将以上语法强加给用户。无需调用 Delegate.Combine(),仅使用 +=
即可组合这两个代理:
MyClass.LogHandler lh = null;
lh += new MyClass.LogHandler(Logger);
lh += new MyClass.LogHandler(fl.Logger);
这样就简洁多了。要从多播代理中删除一个代理,可调用 Delegate.Remove() 或使用 -= 运算符(我知道自己会用哪一个)。
当你调用多播代理时,就会按出现顺序对调用列表中的代理进行同步调用。如果此过程中出现了错误,执行过程即被中断。
如果您想更严格地控制调用顺序(例如要进行万无一失的调用),则可以从代理中获取调用列表,然后自行调用这些函数。以下是一个示例:
foreach (LogHandler logHandler in lh.GetInvocationList())
{
try
{
logHandler(message);
}
catch (Exception e)
{
// 在这里处理异常情况吗?
}
}
代码只是将每次调用包装在一个 try-catch 对中,这样在一个调用处理(handler)中引发的异常就不会妨碍对其它调用处理(handler)的激活。
事件 (Events)
我们已经对代理进行了较长时间的讨论,现在该谈一谈事件了。一个显而易见的问题就是:“既然我们已经有了代理,为什么还需要事件?”
回答这个问题的最好方法就是考虑用户界面对象所发生的事件。例如,一个按钮可能有公共的“Click”代理。我们可将一个函数挂接到该代理上,这样当单击此按钮时,就可以调用该代理。例如:
Button.Click = new Button.ClickHandler(ClickFunction);
它表示当单击此按钮时,将调用 ClickFunction()。
小测验:上述代码是否存在问题?我们忘记了什么?
答案是 ,我们忘记使用 +=
而直接分配了代理。这表示其它任何挂接到“Button.Click”的代理现在都将解除挂接。“Button.Click”应该是公共的,以便其它对象可以对其进行访问,因此上述情况将无法避免。同样,要删除代理,用户可能会编写以下代码:
Button.Click = null;
这将删除所有代理。
这些情形极其糟糕,因为在许多情况下只挂接了一个代理,问题不会明显地表现为bug。随后,当挂接了另一个代理时,事情就糟了!
事件在代理模型上添加了一层保护。这里有一个支持事件的对象例子:
public class MyObject
{
public delegate void ClickHandler(object sender, EventArgs
e);
public event ClickHandler Click;
protected void OnClick()
{
if (Click != null)
Click(this, null);
}
}
ClickHandler
代理使用事件代理的标准模式来定义事件的签名。它以handler的名字结尾,带有两个参数。第一个参数是发送此事件的对象,第二个参数用于传递伴随事件发生的信息。本例中没有要传递的信息,因此直接使用
EventArgs;但是如果有数据要传递,则使用从 EventArgs 派生的类(例如 MouseEventArgs)。
“Click”事件的声明做两件事情:首先,它声明一个名为“Click”的代理成员变量,在类的内部使用。其次,它声明一个名为“Click”的事件,该事件可按照常规访问规则从类的外部进行使用(在此例中,事件为公共事件)。
一个像OnClick()这样的函数通常包含进去以便该类型或从该类型的派生类型可以触发事件。由于“Click”是代理,您将会注意到,用来触发事件的代码与代理的代码相同。
与代理类似,我们使用 += 和 -= 来挂接或解除事件挂接,但与代理不同的是,仅可对事件执行这些操作。这可确保不会发生先前所讨论的两种错误。
使用事件是很简单的事情。
class Test
{
static void ClickFunction(object sender, EventArgs args)
{
// process the event here.
}
public static void Main()
{
MyObject myObject = new MyObject();
myObject.Click += new
MyObject.ClickHandler(ClickFunction);
}
}
我们创建一个与代理签名相匹配的静态函数或成员函数,然后用 += 向事件中添加代理的一个新实例。