What I am trying to achieve is to create convenient syntax for calling a callback on an event only once.
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;
}
}
This should allow us to write something like this:
obj.OnSomething.Once((sender, obj) => Console.WriteLine(obj));
Note that OnSomething is an event EventHandler of some type.
The problem is that I get the error message:
Error CS0070: The event
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)
Is there no way to achieve this that easily? Do I have to remove event from OnSomething and make it a simple delegate?
Unfortunately, you can't have a nice syntax here. Like the error message says, all you can do with an event field from outside of its defining class is referencing it at the left of +=
or -=
.
Here's the method that Rx uses to bind an observable to an event:
var observable = Observable.FromEventPattern(
handler => obj.OnSomething += handler,
handler => obj.OnSomething -= handler);
Basically, FromEventPattern
is a helper that takes two lambdas: one for subscription, and the other for unsubscription. You could use a similar pattern, or just use Rx to achieve the same effect:
Observable.FromEventPattern(h => obj.OnSomething += h, h => obj.OnSomething -= h)
.Take(1)
.Subscribe(e => ...);
On a side note, this will keep a reference to obj
from the lambda used in Subscribe
(there are intermediary glue objects but this is irrelevant). This means that if the event is never called, and if the lambda is long-lived, then obj
won't be eligible for GC (a situation called event memory leak). This may or may not be a problem for your case.
An alternative approach would be to return the callback. In the extension method the callback is deleted/removed. The drawback of this approach is still the fact that the event handler function needs to be defined separately and can not be a lambda.
The extension method:
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;
}
};
}
}
A demo class that has different events:
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); }
}
}
}
Usage of the Demo class:
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();
And the output is:
0
echo string 0
i = 0
echo int 0
1
i = 1
2
i = 2
3
i = 3
4
i = 4
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With