一次性通用事件调用?

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

public static class Extensions { public static void Once(this EventHandler @event, EventHandler callback) { EventHandler 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’(CS0070)之外使用时(SharpTox.Tests)

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

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

这是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 => ...); 

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

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

扩展方法:

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

具有不同事件的演示类:

 public class Demo { public event EventHandler StringEvent = null; public event EventHandler 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 handlerString = (s, e) => { Console.WriteLine("echo string {0}", e); }; Action 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 

您已在此处发布了类似问题的答案。

语法看起来有点像这样:

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