首页 > 解决方案 > 附加事件处理程序只调用一次

问题描述

我目前正在尝试编写一个扩展函数,以便能够轻松附加一个仅在事件触发时使用一次,然后取消订阅的操作。

我正在尝试这样的事情:

public static void AttachOnce<TEventArgs>([NotNull] this EventHandler<TEventArgs> me, [NotNull] Action<object, TEventArgs> action)
    where TEventArgs : System.EventArgs
{
    var handler = me;
    EventHandler<TEventArgs> wrappedAction = null;
    wrappedAction = (sender, args) =>
    {
        action(sender, args);
        handler -= wrappedAction;
    };
    handler += wrappedAction;
}

但是 ReSharper 抱怨取消订阅handler"Access to modified closure"

我知道这意味着什么,所以我已经为闭包创建了局部变量,但它似乎并没有解决它。这里有什么失败?

直接硬编码代码有效。像这样的东西:

var file = new FileSystemWatcher("path/to/file");

FileSystemEventHandler handler = null;
handler = (sender, args) =>
{
    // My action code here
    file.Changed -= handler;
};
file.Changed += handler;

编辑1(2018-10-09 11:43 CET):

我可能只是太快了,在彻底思考之前问了一个问题。
您不能在事件上创建扩展方法。完全没有。这在 C# 中是不可能的。所以我什至无法测试为什么 ReSharper 会抱怨以及它是否正确,因为类似的调用file.Changed.AttachOnce(action)是无效的。它说“事件 'Changed' 只能出现在 += 或 -=" 的左侧

我找到了更多类似请求/问题的来源:
http ://www.hardkoded.com/blog/csharp-wishlist-extension-for-events
一次通用事件调用?

标签: c#eventsextension-methods

解决方案


我一直在考虑一种不同但更简单的方法,使用“自分离”内联处理程序,其使用方式如下:

obj.Event += (s, e) =>
{
    Detach(obj, nameof(obj.Event));
    // ..do stuff..
};

Detach方法看起来像这样,可以放在任何你喜欢的地方(很可能是静态帮助类):

public static void Detach(object obj, string eventName)
{
    var caller = new StackTrace().GetFrame(1).GetMethod();
    var type = obj.GetType();
    foreach (var field in type.GetFields(BindingFlags.Instance | BindingFlags.NonPublic))
    {
        if (typeof(Delegate).IsAssignableFrom(field.FieldType))
        {
            var handler = (field.GetValue(obj) as Delegate)?.GetInvocationList().FirstOrDefault(m => m.Method.Equals(caller));
            if (handler != null)
            {
                type.GetEvent(eventName).RemoveEventHandler(obj, handler);
                return;
            }
        }
    }
}

因此,对于您的示例,代码如下所示:

file.Changed += (s, e) =>
{
    Detach(file, nameof(file.Changed));
    // My action code here
};

推荐阅读