在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表达式,但是......
那么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()
}
- 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);
}
真的很遗憾。
答案 0 :(得分: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
并在必要时使用它们取消订阅。
答案 1 :(得分:9)
你会接受“有一些非常聪明的人在研究C#语言投影”作为答案吗?
更严重的是,您发现的是事件模式的低级ABI(二进制)实现,C#语言投影知道这种模式并知道如何将其公开为C#事件。 CLR中有一些实现此映射的类。