我想要实现的是创建方便的语法,仅在事件上调用一次回调。
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中删除事件并将其设为简单的委托?
答案 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;
}
}
}
}