如何取消注册和注册可等待的事件处理程序?

10

在我的代码中,我需要注销和注册一个事件处理程序。这个方法非常完美:

_favoritsImageView.Click -= _favoritsImageView_Click(this, new CustomeClickEventArgs(item));
_favoritsImageView.Click += _favoritsImageView_Click(this, new CustomeClickEventArgs(item));
void _favoritsImageView_Click(object sender, CustomeClickEventArgs e)
{
       // handles the event
}

但对于可等待的事件处理程序,我必须使用这种语法:

_favoritsImageView.Click -= async (s, e) => 
{ await _favoritsImageView_ClickAsync(s, new CustomeClickEventArgs(item)); };

_favoritsImageView.Click += async (s, e) => 
{ await _favoritsImageView_ClickAsync(s, new CustomeClickEventArgs(item)); };

async Task _favoritsImageView_ClickAsync(object sender, CustomeClickEventArgs e)
{
       // does async task
}

这是行不通的。因为匿名方法没有相同的引用。因此,第一行无法取消已注册的处理程序。最终,第二行会向单击事件添加额外的事件处理程序。

我需要使用哪种语法来添加和删除异步事件处理程序?

感谢任何建议。


我有点困惑,你说“完美运行”的语法是不可行的。除非_favoritsImageView_Click是一个返回委托的方法,但这样做真的很奇怪。 - svick
@svick,我在示例代码中添加了_favoritsImageView_Click。有什么问题吗? - a.toraby
问题在于 _favoritsImageView.Click += _favoritsImageView_Click(this, new CustomeClickEventArgs(item)); 这一行无法编译。 - svick
2个回答

10

我需要使用哪种语法来添加和删除异步事件处理程序?

与常规事件处理程序所需使用的相同语法。您需要将委托保存在某个地方,以便稍后注销它:

private EventHandler eventHandler = 
                            new EventHandler(async (s, e) => await FooAsync(s, e));

public async void SomeOtherEventHandler()
{
    var m = new M();
    m.X += eventHandler;
    m.OnFoo();
    m.X -= eventHandler;
    m.OnFoo();
}

public async Task FooAsync(object sender, EventArgs e)
{
    await Task.Delay(1000);
    Debug.WriteLine("Yay event handler");
}

public class M
{
    public event EventHandler X;
    public void OnX()
    {
        // If you're using C#-6, then X?.Invoke(null, EventArgs.Empty);

        var localX = X;
        if (localX != null)
            localX(null, EventArgs.Empty);
    }
}

编辑:

@svick建议另一种解决方案,将方法直接设置为async void并直接注册,这绝对更短:

public async void SomeOtherEventHandler()
{
    var m = new M();
    m.X += FooAsync;
    m.OnFoo();
    m.X -= FooAsync;
    m.OnFoo();
}

public async void FooAsync(object sender, EventArgs e)
{
    await Task.Delay(1000);
    Debug.WriteLine("Yay event handler");
}

1
只有在事件处理程序是lambda表达式时,才需要保存委托。我认为一般来说,更好的解决方案是将事件处理程序编写为方法(在这种情况下是async void方法),这将使代码更简单。 - svick
@svick 这是个好主意。添加了另一个版本,直接注册。 - Yuval Itzchakov
我认为了解在这种情况下任务返回和void返回之间的区别以及为什么@svick的答案很好会很有帮助 :) https://dev59.com/uGsz5IYBdhLWcg3wOVL5#8043882 - Nicolas Bodin
这是我正在寻找的解决方案,如果您在事件处理程序中使用异步方法并且想要启用/禁用/替换它们。 - Toolkit

6
同步代码大致如下:
private void FavoritsImageView_Click(object sender, CustomeClickEventArgs args)
{
    // your synchronous code here
}

…

_favoritsImageView.Click += FavoritsImageView_Click;
_favoritsImageView.Click -= FavoritsImageView_Click;

异步版本看起来几乎相同,您只需要将async添加到该方法中即可:
private async void FavoritsImageView_Click(object sender, CustomeClickEventArgs args)
{
    // your asynchronous code here
}

…

_favoritsImageView.Click += FavoritsImageView_Click;
_favoritsImageView.Click -= FavoritsImageView_Click;

请注意,事件处理程序是几乎唯一需要使用 async void 的地方。在大多数其他情况下,同步的 void 方法应该被转换为 async Task 方法。


我已经尝试过了。编译器错误:无法隐式转换类型“void”为“System.EventHandler”。 - a.toraby
@a.toraby,当我使用你的语法+= FavoritsImageView_Click(this, new CustomeClickEventArgs(item));时,我会得到那个错误。但这不是我建议的方法。 - svick
@downvoter 能否解释一下你认为我的回答有什么问题? - svick

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