我试图实现的是创建方便的语法,以便只调用一次事件的回调。
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' (CS0070) (SharpTox.Tests)
难道就没有办法那么容易地做到这一点吗?我是否必须从 OnSomething 中删除事件并使其成为简单的委托?
不幸的是,你在这里不能有一个很好的语法。就像错误消息所说的那样,您只能从其定义类外部对事件字段执行+=
或-=
的左侧引用它。
下面是 Rx 用于将可观察量绑定到事件的方法:
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<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); }
}
}
}
演示类的用法:
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
您在此处发布了类似问题的答案。
语法看起来有点像这样:
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;
}
}
}
}