附加事件处理程序仅被调用一次

时间:2018-10-09 08:49:59

标签: c# events extension-methods

我目前正在尝试编写扩展功能,以便能够轻松附加仅在触发事件后才取消订阅一次的动作。

我正在尝试这样的事情:

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:xd="http://www.oxygenxml.com/ns/doc/xsl"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
exclude-result-prefixes="xs xd"
version="2.0">

<xsl:output method="xml" version="1.0" encoding="utf-8" omit-xml-declaration="no" indent="no" />
<xsl:strip-space elements="*" />

<xsl:template match="/">
    <clients>
        <xsl:attribute name="xsi:noNamespaceSchemaLocation">setClients.xsd</xsl:attribute>
        <xsl:attribute name="encryptedData">N</xsl:attribute>

        <xsl:for-each select="xsi:pointOfSale">
            <xsl:if test="@mode='UPDATE'">
                <client>
                    <xsl:attribute name="cstclavep" select="id"/>
                    <address>
                        <STREETNAME><xsl:value-of select="xsi:info/xsi:address/xsi:streetName"/></STREETNAME>
                    </address>
                </client>
            </xsl:if>
        </xsl:for-each>
    </clients>
</xsl:template>

但是ReSharper抱怨取消订阅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; } “访问修改后的封包”

我知道这意味着什么,所以我已经为闭包创建了局部变量,但是它似乎无法解决它。这里出了什么问题?

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

handler

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

我可能太快了,在彻底考虑之前先问一个问题。
您无法在事件上创建扩展方法。完全没有在C#中是不可能的。因此,我什至无法测试ReSharper为何抱怨,是否正确,因为var file = new FileSystemWatcher("path/to/file"); FileSystemEventHandler handler = null; handler = (sender, args) => { // My action code here file.Changed -= handler; }; file.Changed += handler; 之类的呼叫无效。它说“事件'Changed'只能出现在+ =或-=” 的左侧。

我发现了更多类似请求/问题的来源:
http://www.hardkoded.com/blog/csharp-wishlist-extension-for-events
One time generic event call?

3 个答案:

答案 0 :(得分:1)

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

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
};

答案 1 :(得分:0)

在这种情况下,可以。 ReSharper只是警告handler在您声明的时间和执行时间之间是不同的。

答案 2 :(得分:0)

不确定您要如何设计扩展方法,但这也许可以帮助您入门:

public static void OnChangedOnce(this FileSystemWatcher instance, Action<object, FileSystemEventArgs> action)
{
    var file = instance;
    var wrappedAction = action;
    FileSystemEventHandler handler = null;
    handler = (object sender, FileSystemEventArgs args) =>
    {
        wrappedAction(sender, args);
        file.Changed -= handler;
    };
    file.Changed += handler;
    file.EnableRaisingEvents = true; // mandatory
}

var file = new FileSystemWatcher("path/to/file");
file.OnChangedOnce((sender, args)) =>
{
    // your action here
});