WinRT事件如何与.NET互操作

8
在Rx团队最新的视频Bart De Smet: Rx Update - .NET 4.5, Async, WinRT中,我发现WinRT事件通过一些非常奇怪的元数据暴露给了.NET,更确切地说是add_/remove_配对方法的签名:
EventRegistrationToken add_MyEvent(EventHandler<MyEventArgs> handler) { … }
void remove_MyEvent(EventRegistrationToken registrationToken) { … }

这看起来非常棒,可以通过“处理”注册令牌(Rx也采用同样的方法,从Subscribe()方法返回实例)来取消事件订阅。因此,现在可以轻松地取消来自事件的lambda表达式,但是......C#如何允许使用此类事件?在.NET中,可以使用一个委托实例订阅一个方法(静态和实例),并使用完全不同的指向相同方法的委托实例取消订阅。那么如果我只是在C#中使用WinRT事件并取消某个委托类型实例的订阅...编译器从哪里获取正确的EventRegistrationToken?所有这些魔法都是如何工作的?
--更新--
实际上,EventRegistrationToken不允许通过调用某种Dispose()方法简单地取消订阅,这真的很遗憾。
public struct EventRegistrationToken
{
    internal ulong Value { get; }
    internal EventRegistrationToken(ulong value)
    public static bool operator ==(EventRegistrationToken left, EventRegistrationToken right)
    public static bool operator !=(EventRegistrationToken left, EventRegistrationToken right)
    public override bool Equals(object obj)
    public override int GetHashCode()
}

-- 更新2 --

WinRT互操作性实际上使用全局注册令牌表来订阅带有托管对象的WinRT事件。例如,删除处理程序的交互操作代码如下:

internal static void RemoveEventHandler<T>(Action<EventRegistrationToken> removeMethod, T handler)
{
  object target = removeMethod.Target;
  var eventRegistrationTokenTable = WindowsRuntimeMarshal.ManagedEventRegistrationImpl.GetEventRegistrationTokenTable(target, removeMethod);
  EventRegistrationToken obj2;
  lock (eventRegistrationTokenTable)
  {
    List<EventRegistrationToken> list;
    if (!eventRegistrationTokenTable.TryGetValue(handler, out list)) return;
    if (list == null || list.Count == 0) return;
    int index = list.Count - 1;
    obj2 = list[index];
    list.RemoveAt(index);
  }
  removeMethod(obj2);
}

这真的很不幸。

3
请注意,你所描述的一切都是实现细节,随时可能更改。你永远不应该依赖于任何你逆向工程得出的行为。 - Larry Osterman
2个回答

14

当你添加或移除一个WinRT事件的委托,就像这样:

this.Loaded += MainPage_Loaded;

this.Loaded -= MainPage_Loaded;

看起来你正在使用普通的.Net事件。但是这段代码实际上被编译成以下内容(Reflector似乎有些难以反编译WinRT代码,但我认为这才是代码实际执行的操作):

WindowsRuntimeMarshal.AddEventHandler<RoutedEventHandler>(
    new Func<RoutedEventHandler, EventRegistrationToken>(this.add_Loaded),
    new Action<EventRegistrationToken>(remove_Loaded),
    new RoutedEventHandler(this.MainPage_Loaded));

WindowsRuntimeMarshal.RemoveEventHandler<RoutedEventHandler>(
    new Action<EventRegistrationToken>(this.remove_Loaded),
    new RoutedEventHandler(this.MainPage_Loaded));

这段代码实际上无法编译,因为你无法从C#中访问add_remove_方法。但是你可以在IL中访问它们,这正是编译器所做的。

看起来WindosRuntimeMarshal会保留所有这些EventRegistrationToken并在必要时使用它们进行取消订阅。


哦不,看起来它真的保留了所有受管理对象订阅的全局表... - controlflow
MSDN有一篇非常好的文章,介绍了Windows Runtime组件中的自定义事件和事件访问器。@svick,你并没有离谱。 - zastrowm

9
你是否能接受“有一些非常聪明的人正在开发C#语言投影”作为答案?
更加严肃地讲,你所发现的是事件模式的底层ABI(二进制)实现,C#语言投影了解这种模式,并知道如何将其作为C#事件暴露出来。CLR内部有实现此映射的类。

1
听起来更像是C# WinRT团队在给猪涂口红 :) - Ana Betts
4
为什么要往猪身上涂口红?这正是语言映射的工作原理。Windows Runtime拥有一种事件模式(以及异步模式、集合模式和其他模式)。所有公开事件的API都使用相同的模式。语言映射的任务是采用使用该模式的API,并使其对该语言的用户感到自然和熟悉。Windows Runtime不能简单地采用C#事件模式,因为有两个原因:Windows Runtime必须是与语言无关的,而且运行时使用引用计数进行生存期管理。 - Larry Osterman
4
很不幸,这也是正确的。事件的低级投影很简单 - 有一个添加事件和一个移除事件的方法,添加方法采用委托并返回一个令牌,移除方法使用该令牌。当事件被触发时,调用该委托。CLR将此低级机制映射到CLR兼容版本中。所使用类的实际名称是实现细节,可能会在不同版本之间更改。JavaScript具有完全不同的实现机制,C++也是如此。所有实现都可能会更改。 - Larry Osterman

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接