扩展 .NET 事件功能






4.48/5 (14投票s)
本文解释了如何使用额外的功能来扩展标准的 .NET 事件和委托机制。

引言
任何使用过 .NET 事件和委托的人都明白它们强大的功能和易用性。在 C# 或 VB.NET 类中创建事件并订阅它已经变得相当简单。此外,这是一个实现(例如)响应式用户界面的有用机制。如果设计得当,您就不需要轮询对象的状态,而是可以通过事件来信号化其状态。
但是,尽管易于使用,仍然缺少一些功能。有时,不仅仅是希望侦听事件,而是在订阅事件时触发事件一次可能很有用。标准的 .NET 事件只允许您在不知道初始状态/值的情况下进行订阅,但只要事件未被触发,您就无法正确显示它。通常,您需要通过单独的方法或属性来获取(轮询)值,才能了解对象的当前状态/值。
在本文中,我将用 C# 描述如何为您的类添加此额外的事件功能。稍加修改,您也可以将其转换为 VB.NET。
在文章末尾,我还会展示如何从 C# 类发送事件,以及如何在不将您的类变成 ActiveX 控件的情况下从 JavaScript 订阅它。在正常情况下,您只能从 HTML 或 ASP 页面中运行的 JavaScript 订阅 ActiveX 控件对象。
背景
乍一看,C# 委托类看起来与其他普通类无异。但一旦您尝试从它派生,扩展其基本功能,C# 编译器就会发出错误,不允许从委托类派生。事件也是如此。委托和事件是特殊的编译器生成的类,被以特殊方式对待。唯一的解决方案是创建一个新类,我们可以在其中实现我们的新事件代码。
代码概览
情况 1:标准事件
首先,我们来看看如何在 C# 中使用普通事件。首先,我们创建 Clocks.Clock
类,其中包含如下示例代码。
private Clocks.Clock m_clock = new Clocks.Clock();
接下来,我们订阅标准的 C# TickEvent
m_clock.TickEvents += new Clocks.TickDelegate(Clock_Tick1);
其中 Clock_Tick1
是事件被触发时将被调用的函数。为了让 Clocks.Clock
类触发标准事件,它现在调用
// Trigger standard event
if (TickEvent != null) TickEvent(ticks);
情况 2:基类 ExtendedClockDelegate
如前所述,我们需要创建一个新的委托类来实现我们的新功能。这就是 ExtendedClockDelegate
类。我们首先要做的是添加相同的标准事件机制。这将允许我们如下订阅事件:
m_clock.ExtendedClockEvents.TickEvents += new Clocks.TickDelegate(
Clock_Tick2);
为了让 Clocks.Clock
类触发扩展事件,它现在调用
// Trigger extended event
m_extendedClockEvents.TriggerEvent(ticks);
情况 3:将基类 ExtendedClockDelegate 事件与标准事件对齐
如果您仔细查看前两个代码示例,您会注意到通过 ExtendedClockDelegate
类订阅事件存在一个额外的“间接”。我们能否以某种方式避免这种情况?答案是肯定的。如果我们重载 ExtendedClockDelegate
的 operator +
和 operator -
,我们就可以像情况 1 那样再次编写。
m_clock.ExtendedClockEvents += new Clocks.TickDelegate(Clock_Tick3);
情况 4:进一步扩展 ExtendedClockDelegate 类
到目前为止,我们并没有真正添加很多新功能。现在让我们添加额外的 AddEventHandler
和 RemoveEventHandler
方法。这些方法允许我们订阅 ExtendedClockDelegate
类的事件,但我们可以预见额外的参数来定义我们希望如何订阅。例如,我们可以允许订阅一个事件,并在我们订阅时立即触发事件以调用我们的回调方法。在我们的示例中,我们将传递最后一次触发事件时使用的时间值。代码现在看起来如下。
m_clock.ExtendedClockEvents.AddEventHandler(new Clocks.TickDelegate(
Clock_Tick4), Clocks.EventCondition.FireToCurrentSubscriber);
情况 5:情况 4 的变体
在最后一个示例中,我们更改了 AddEventHandler
和 RemoveEventHandler
方法的行为。现在,我们允许订阅者代码在订阅时定义回调方法的时间值。代码现在看起来如下。
m_clock.ExtendedClockEvents.AddEventHandler(new Clocks.TickDelegate(
Clock_Tick5), Clocks.EventCondition.FireToCurrentSubscriber, 1234);
订阅完成时,回调方法将立即被调用,时间值设置为 1234。
从 JavaScript 订阅 C# 事件
从 HTML 或 ASP 运行 JavaScript 时,只有当您的对象是完整的 ActiveX 控件时,才可能订阅事件,例如 button_onclick() 事件。但大多数日常类都不是控件。那么如何订阅非 ActiveX 控件的事件呢?嗯,Java 运行时会将 JavaScript 回调函数转换为一个具有默认方法的 COM 对象(老式的纯自动化功能)。如果您为 C# 类添加一个特殊的属性,您可以在其中设置或获取一个对象,您可以将 JavaScript 回调方法分配给我的类属性(我称之为 ScriptCallbackObject
)。ExtendedClockDelegate
类如何知道如何调用 COM 对象的默认方法?答案是:.NET 强大的反射功能。请注意,涉及的 C# 类需要注册 COM 互操作,因此需要“[System.Runtime.InteropServices.xxx]”属性语句。以下代码片段显示了如何实现这一点。
从 JavaScript 代码
<script type="text/javascript" language="jscript">
...
var clock = new ActiveXObject("Clocks.clock");
var extendedClockEvents = clock.ExtendedClockEvents();
// Here you assign (subscribe to) your callback method!
extendedClockEvents.ScriptCallbackObject = clock_Callback;
...
function clock_Callback(time)
{
document.getElementById("text_tag").innerHTML = time;
}
...
</script>
从 C# 代码
System.Type t = scriptCallbackObject.GetType();
t.InvokeMember("", System.Reflection.BindingFlags.InvokeMethod, null,
scriptCallbackObject, new object[] { time });
请注意 InvokeMember
的第一个参数是空字符串“”。这将执行 COM 对象的默认方法,而无需知道其名称。示例包包含一个 HTML 页面,演示了如何执行此操作。
关于示例代码
示例程序包含一个 C# 库,其中有一个 Clocks.Clock
类。该类包含一个标准的 .NET 事件和 Clocks.ExtendedClockDelegate
类。后者类包含对标准委托的所有扩展。根据 AddEventHandler
或 RemoveEventHandler
方法的额外参数,时钟类默认每秒触发一次事件。根据额外参数,事件还可以根据您的命令另外触发。
Clock 可执行程序演示了 Clocks.Clock
类的用法。它将以所有可能的方式订阅其事件。
event.html 文件演示了如何在 JavaScript 中使用 C# 事件。请注意,要使此示例正常工作,您需要为 COM 互操作注册 clocks.dll。csproj 文件在生成后部分包含了此命令。
摘要
我希望本文能够为您提供足够的见解,说明如何扩展标准的 C#(或 VB.NET)事件机制。下图显示了源代码中可用的可能性。在此列表的顶部,您还可以使用 += 和 -= 运算符。

当然,作为程序员,您可以根据自己的需要进一步扩展其功能。