原文地址:devblogs.microsoft.com/oldnewthing…
原文作者:devblogs.microsoft.com/oldnewthing…
发布时间:2019年5月23日
在C++/CX中,有两种方法可以创建事件处理程序。作为一个(对象,方法)对,或者作为一个lambda。而根据你的方式不同,寿命规则也不同。
当你用一个对象和一个方法创建一个delegate时,对象是通过弱引用来捕获的。当委托人被调用时,运行时首先尝试将弱引用解析回强引用。如果成功,那么它就会调用方法。如果不成功,它就会引发Platform::DisconnectedException,我们在前面看到,这是给事件源的一个信号,说明这个delegate应该被自动取消注册,因为它再也不会成功了。
当你用lambda创建一个delegate时,你会将对象捕获到你的lambda中,只要delegate存在,这些对象就会保持活力。对于事件处理程序来说,委托人一般¹继续存在,直到处理程序被取消注册。在C++/CX中,帽子指针是强引用,所以如果你捕获了一个帽子指针,你就捕获了一个对象的强引用。特别是在ref类的方法中,这是一个指向包围类的帽子指针,所以捕获这个就捕获了对包围类的强引用。这一点在文档中是叫出来的。
一个命名的函数通过弱引用来捕获 "this "指针, 但一个lambda通过强引用来捕获它 并创建一个循环引用。
这里的文档有点假设,lambda中捕获的对象又包含了对持有delegate的对象的引用。如果不是这样,那么你就没有(立即)循环引用。
using namespace Windows::Devices::Enumeration;
ref class Circular
{
DeviceWatcher^ watcher;
public:
Circular()
{
watcher = DeviceInformation::CreateWatcher();
watcher.Added += ref new TypedEventHandler<
DeviceWatcher^, DeviceInformation^>(
[this](DeviceWatcher^ sender, DeviceInformation^ info)
{
...
});
}
};
上面的例子用lambda创建了一个C++/CX delegate,这个lambda捕获了这个。由于这是一个ref类,所以一个强引用被捕获到lambda中,我们创建了一个循环引用。
delegate -> Circular -> DeviceWatcher -> delegate
与C#一样,你将不得不手动中断这个循环引用。你不能打破从委托人到循环对象的箭头(因为它是在lambda里面捕获的),所以你的选择是取消从事件中注册委托人(打破从DeviceWatcher到委托人的箭头),或者清空循环对象对DeviceWatcher的引用。
另一方面,这个版本用一个对象和一个方法指针来创建委托人。
ref class Circular
{
DeviceWatcher^ watcher;
public:
Circular()
{
watcher = DeviceInformation::CreateWatcher();
watcher.Added += ref new TypedEventHandler<
DeviceWatcher^, DeviceInformation^>(
this, &Circular::OnDeviceAdded);
}
private:
void OnDeviceAdded(DeviceWatcher^ sender, DeviceInformation^ info)
{
...
}
};
对象是通过弱引用捕捉到委托人的,这意味着不存在循环引用。
注意,通过弱引用捕获对象意味着委托人不会让对象存活。如果你想让对象保持活力,你就必须自己保持它的活力。
最后需要注意的是,当通过XAML标记创建事件处理程序时,产生的delegate是(对象,方法)类型的。
<!-- XAML -->
<Page x:Name="AwesomePage" ...>
...
<Button Click="Button_Click" >
...
</Page>
当您编写上述XAML时,创建委托就像您编写了
thatButton.Click += ref new EventHandler<RoutedEventArgs^>
(this, &AwesomePage::Button_Click);
因此,你不必担心XAML标记事件处理程序创建的循环引用。
¹ 当然,您可以通过在创建委托后保留一个显式引用来延长委托的寿命。但人们很少这样做,如果你这样做,你就知道你要做什么了。