One-time general call?

I am trying to create a 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 some type of EventHandler.

The problem is I am getting the error:

Error CS0070: SharpTox.Core.Tox.OnFriendMessage' can only appear on the left hand side of += or -= when used outside of the type

SharpTox.Core.Tox ' event (CS0070) (SharpTox.Tests)

Isn't there a way to easily achieve this? Do I need to remove the event from OnSomething and make it a simple delegate?

+3


source to share


2 answers


Unfortunately, there can be no strong syntax here. As with the error message, anything you can do with an event field outside of its defining class refers to it to 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 accepts two lambdas: one for subscribing and the other for unsubscribing. You can 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 contain a reference to obj

from the lambda used in Subscribe

(there are intermediate glue objects, but that doesn't matter). This means that if the event is never called, and if the lambda is persistent, then obj

it will not be eligible for GC (a situation called an event memory leak). This may or may not be a problem for your case.

+8


source


An alternative approach would be to return a callback. In the extension method, the callback is removed / removed. The disadvantage of this approach is the fact that the event handler function must be defined separately and cannot be a lambda.

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

      

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

      



Using 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 result:

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

      

+2


source







All Articles