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 做同样的事情,IDisposable从方法返回实例Subscribe())。因此,可以轻松地从事件中取消订阅lamba-expressions,但是......

那么 C# 如何允许处理这种事件呢?在 .NET 中,可以使用委托上的一个实例订阅方法(静态和实例),并完全取消订阅指向同一方法的另一个委托实例。因此,如果我使用 WinRT 事件并且只是取消订阅 C# 中的某些委托类型实例......编译器在哪里得到正确的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);
}

这真的很可悲。

4

2 回答 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_and方法。remove_但是你可以在 IL 中做到这一点,而这正是编译器所做的。

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

于 2011-10-14T23:52:29.093 回答
9

你会接受“有一些非常聪明的人致力于 C# 语言投影”作为答案吗?

更严重的是,您发现的是事件模式的低级 ABI(二进制)实现,C# 语言投影知道这种模式并知道如何将其公开为 C# 事件。CLR 中有实现此映射的类。

于 2011-10-15T06:16:09.683 回答