WinRT事件如何与.NET互操作

con*_*low 8 c# events event-handling system.reactive windows-runtime

在Rx团队Bart De Smet的最新视频中:Rx Update - .NET 4.5,Async,WinRT我看到WinRT事件通过一些非常奇怪的元数据暴露给.NET,更多的是preciesly - add_/ remove_pair方法签名:

EventRegistrationToken add_MyEvent(EventHandler<MyEventArgs> handler) { … }
void remove_MyEvent(EventRegistrationToken registrationToken) { … }
Run Code Online (Sandbox Code Playgroud)

它看起来非常棒,允许通过"处理"注册令牌取消订阅事件(Rx做同样的事情,IDisposableSubscribe()方法返回实例).因此,可以轻松取消订阅事件中的lamba表达式,但......

那么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()
}
Run Code Online (Sandbox Code Playgroud)

- update2 -

当使用托管对象订阅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);
}
Run Code Online (Sandbox Code Playgroud)

这真的很遗憾.

svi*_*ick 14

当您向WinRT事件添加或删除委托时,如下所示:

this.Loaded += MainPage_Loaded;

this.Loaded -= MainPage_Loaded;
Run Code Online (Sandbox Code Playgroud)

看起来就像你正在使用普通的.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));
Run Code Online (Sandbox Code Playgroud)

此代码实际上不会编译,因为您无法从C#访问add_remove_方法.但你可以在IL中,这正是编译器所做的.

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


Rei*_*man 9

你会接受"有一些非常聪明的人在研究C#语言投影"作为答案吗?

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

  • 为什么口红猪?这正是语言预测的工作方式.Windows运行时具有事件模式(以及异步模式和集合模式以及其他模式).每个公开事件的API都使用相同的模式.语言投影的工作是采用使用该模式的API,使其对该语言的用户感到自然和熟悉.Windows运行时不能简单地采用C#事件模式,原因有两个:Windows运行时必须与语言无关,运行时使用引用计数进行生命周期管理. (4认同)
  • 不幸的是,它也是正确的.事件的低级别投影很简单 - 有一个事件添加和一个事件删除方法,add接受一个委托并返回一个令牌,remove获取令牌.触发事件时,将调用该委托.CLR将此低级机制映射到CLR兼容版本.所使用的类的实际名称是实现细节,可能会从一个构建更改为另一个构建.JavaScript与C++具有完全不同的机制实现.并且所有实现都可能发生变化. (4认同)