Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

what's the best way to implement chained events in C#

Tags:

c#

.net

events

I have the following scenario. The client code only has access to FooHandler, not directly to Foo instances.

public delegate void FooLoaded(object sender, EventArgs e);

class Foo {
    public event FooLoaded loaded;
    /* ... some code ... */
    public void Load() { load_asynchronously(); }
    public void callMeWhenLoadingIsDone() { loaded(this,EventArgs.Empty); }
}

class FooHandler {
    public event FooLoaded OneFooLoaded;

    /* ... some code ... */

    public void LoadAllFoos() { 
        foreach (Foo f in FooList) { 
            f.loaded += new FooLoaded(foo_loaded);
            f.Load(); 
        } 
    }

    void foo_loaded(object sender, EventArgs e) {
        OneFooLoaded(this, e);
    }

}

Then the clients would use the OneFooLoaded event of the FooHandler class to get notifications of loading of foos. Is this 'event chaining' the right thing to do? Are there any alternatives? I don't like this (it feels wrong, I cannot precisely express why), but if I want the handler to be the point of access I don't seem to have many alternatives.

like image 408
Vinko Vrsalovic Avatar asked Jul 12 '09 08:07

Vinko Vrsalovic


1 Answers

If it feels wrong because events are more sophisticated and outward-facing than necessary for your internal communications (which I believe is at least partially true considering events can call multiple clients whereas you know you only need to notify one, right?), then I propose the following alternative. Instead of using events to communicate the completion of Foo to FooHandler, since Foo is internal anyway, you could add a callback parameter to the constructor or Load method of Foo, which Foo can call when it is done loading. This parameter can be just a function if you only have one callback, or it can be an interface if you have many. Here's how I think your code would look with the simplified internal interface:

public delegate void FooLoaded(FooHandler sender, EventArgs e);

class Foo
{
  Action<Foo> callback;
  /* ... some code ... */
  public void Load(Action<Foo> callback) { this.callback = callback; load_asynchronously(); }
  public void callMeWhenLoadingIsDone() { callback(this); }
}

class FooHandler
{
  public event FooLoaded OneFooLoaded;

  /* ... some code ... */

  public void LoadAllFoos()
  {
     foreach (Foo f in FooList)
     {
        f.Load(foo_loaded);
     }
  }

  void foo_loaded(Foo foo)
  {
     // Create EventArgs based on values from foo if necessary
     OneFooLoaded(this, null);
  }

}

Notice that this also allows you to be more strongly typed with the FooLoaded delegate.

If, on the other hand, it feels wrong because the event shouldn't have to go through FooHandler to get to the client, then 1) I would dispute that because if the client doesn't want to deal with the individual Foo objects, it shouldn't be sinking events from them at that level either, and 2) If you really wanted to do that, you could implement some public callback interface on Foo even though Foo is private, or use a mechanism like Pavel suggested. I think, however, that clients like the simplicity of implementing fewer event handlers and distinguishing the source within the one handler rather than having to connect (and potentially disconnect) events from dozens of smaller objects.

like image 181
BlueMonkMN Avatar answered Sep 20 '22 02:09

BlueMonkMN