一次性通用事件调用?

时间:2015-01-04 15:08:35

标签: c#

我想要实现的是创建方便的语法,仅在事件上调用一次回调。

public static class Extensions
{
    public static void Once<T>(this EventHandler<T> @event, EventHandler<T> callback)
    {
        EventHandler<T> cb = null;
        cb = (object that, T info) => {
            if (callback != null) {
                callback(that, info);
            }
            @event -= cb;
        };
        @event += cb;
    }
}

这应该允许我们写这样的东西:

obj.OnSomething.Once((sender, obj) => Console.WriteLine(obj));

请注意,OnSomething是某种类型的事件EventHandler。

问题是我收到错误消息:

  

错误CS0070:事件SharpTox.Core.Tox.OnFriendMessage' can only appear on the left hand side of += or -= when used outside of the type SharpTox.Core.Tox&#39; (CS0070)(SharpTox.Tests)

难道没有办法轻松实现这一目标吗?我是否必须从OnSomething中删除事件并将其设为简单的委托?

3 个答案:

答案 0 :(得分:8)

不幸的是,你不能在这里有一个很好的语法。与错误消息一样,您可以使用其定义类外部的事件字段来引用它+=-=左侧。

以下是Rx用于将observable绑定到事件的方法:

var observable = Observable.FromEventPattern(
    handler => obj.OnSomething += handler,
    handler => obj.OnSomething -= handler);

基本上,FromEventPattern是一个带有两个lambda的助手:一个用于订阅,另一个用于取消订阅。您可以使用类似的模式,或者只使用Rx来实现相同的效果:

Observable.FromEventPattern(h => obj.OnSomething += h, h => obj.OnSomething -= h)
          .Take(1)
          .Subscribe(e => ...);

在旁注中,这将保留对obj中使用的lambda的Subscribe的引用(有中间粘合对象,但这是无关紧要的)。这意味着如果从未调用事件,并且lambda是长寿的,则obj将不符合GC的条件(称为事件内存泄漏的情况)。对于您的情况,这可能是也可能不是问题。

答案 1 :(得分:1)

另一种方法是返回回调。在扩展方法中,删除/删除回调。这种方法的缺点仍然是事件处理函数需要单独定义而不能是lambda。

扩展方法:

public static class Extensions
{   
    public static EventHandler<T> Once<T>(this Action<Object, T> callback)
    {
        return (Object s, T e) => 
        {
            if (callback != null) {
                callback(s, e);
                callback = null;
            }
        };
    }
}

具有不同事件的演示类:

public class Demo
{
    public event EventHandler<String> StringEvent = null;
    public event EventHandler<Int32> IntEvent = null;

    public void NotifyOnWork()
    {
        for (int i = 0; i < 5; i++)
        {
            Console.WriteLine(i);
            if (this.StringEvent != null) { this.StringEvent(this, i.ToString()); }
            if (this.IntEvent != null) { this.IntEvent(this, i); }
        }
    }
}

Demo类的用法:

var demo = new Demo();
Action<Object, String> handlerString = (s, e) =>  { Console.WriteLine("echo string {0}", e); };
Action<Object, Int32> handlerInt = (s, e) =>  { Console.WriteLine("echo int {0}", e); };

demo.StringEvent += handlerString.Once();
demo.IntEvent += handlerInt.Once();
demo.StringEvent += (s, e) => Console.WriteLine("i = {0}", e); 
demo.NotifyOnWork();

输出是:

0
echo string 0
i = 0
echo int 0
1
i = 1
2
i = 2
3
i = 3
4
i = 4

答案 2 :(得分:0)

您已经发布了类似问题here的答案。

语法看起来像这样:

obj.Event += (s, e) =>
{
    Detach(s, 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;
            }
        }
    }
}