Question

I have been dappling in C# after coming from several years in PHP. I don't find the language particularly difficult, although there are considerably more constructs for me to get used to.

My question is to help me change way of thinking when using C#. More specifically, to help me think of solutions in a static language when I've been spoiled by a dynamic one.

For my experiment I have created a simple copy of Symfony's EventDispatcher, which is used as so:

// PHP
class Sample
{
    public function __construct()
    {
        $dispatcher = new EventDispatcher();
        $dispatcher->addListener("sample.event", array($this, "onEvent"));
        $dispatcher->dispatch   ("sample.event", new Event());
    }

    public function onEvent(Event $event)
    {
        echo "Event Dispatched";
    }
}

And this is my C# replica of EventDispatcher:

// C#
public delegate void EventListener (EventArgs args);

public class EventDispatcher
{
    protected Dictionary<string, EventListener> Listeners = new Dictionary<string, EventListener> ();

    public void AddListener (string eventName, EventListener listener)
    {
        EventListener l;
        Listeners.TryGetValue (eventName, out l);

        if (null == l) {
            l = listener;
            Listeners.Add (eventName, l);
        } else {
            l += listener;
        }
    }

    public void Dispatch(string eventName, EventArgs args)
    {
        EventListener listener;
        Listeners.TryGetValue (eventName, out listener);

        if (null != listener) {
            listener (args);
        }
    }
}

And finally this is a copy of the PHP example:

// C#
class Application
{
    protected static EventDispatcher Dispatcher = new EventDispatcher();

    public static void Main(string[] args)
    {
        Dispatcher.AddListener ("sample.event", onEvent);
        Dispatcher.Dispatch    ("sample.event", new ErrorEventArgs(new Exception()));
    }

    public static void onEvent(EventArgs args)
    {
        Console.Out.WriteLine ("Event Dispatched");
    }
}

Now the difference here is I am dispatching the event with ErrorEventArgs, a child class of EventArgs. This is fine because the instance is substituted.

However, in PHP I can declare the listener as this to make certain the correct event is given to the method:

// PHP
public function onEvent(ErrorEvent $event)
{
    echo "Event Dispatched";
}

However the same in C# will not work:

// C#
public static void onEvent(ErrorEventArgs args)
{
    Console.Out.WriteLine ("Event Dispatched");
}

Because it does not match the delegate delegate void EventListener (EventArgs args).

Now I know .NET already has an event system and it looks like I am reinventing the wheel, also if you are familiar with Symfony2 you will know that it's event dispatcher is not a typical example of itself (events are usually globalized).

I could in this case only define the methods to conform with the required delegate and use introspection to determine whether the event should be handled. Or I can implement AddListener and Dispatch as generic methods, at the cost of defining the dictionary as Dictionary<string, object>.

So my question boils down to whether I am approaching this the wrong way, whether this type of semi-dynamic programming is suitable, or is there a truely static way of implementing this?

Please bare in mind that this is an example experiment and in the real world I would look for traditional practices to implement this specific scenario.

EDIT:

Using the generics and object dictionary solution, here is the refactor:

public delegate void EventListener<T> (T args);

public class EventDispatcher
{
    protected Dictionary<string, object> Listeners = new Dictionary<string, object> ();

    public void AddListener<T> (string eventName, EventListener<T> listener)
    {
        EventListener<T> l;
        object obj;

        if (Listeners.TryGetValue (eventName, out obj)) {
            l = (EventListener<T>)obj; // handling required here if obj cannot be casted.
            l += listener;
        } else {
            l = listener;
            Listeners.Add (eventName, l);
        }
    }

    public void Dispatch<T> (string eventName, T args)
    {
        EventListener<T> l;
        object obj;

        if (Listeners.TryGetValue (eventName, out obj)) {
            l = (EventListener<T>)obj;
            l (args);
        }
    }
}
Was it helpful?

Solution

One of the most important things when using a programming language is understanding what it's weaknesses are. As many languages have substantially different weaknesses, it is often a very bad idea to directly port code from one to another.

In both examples you have provided, you are presented with a fairly severe typing issue. Even the dictionary has a large weakness, because you can pass in any key with any type, meaning that failed casts are likely.

If I understand what your goal was, you are trying to build a system for events that work through subscriptions and publishing. The best example I've seen of this is PRISM's EventAggregator. It works something like this:

public class EventAggregator : IEventAggregator
{
  private List<PubSubEvent> events;
  public T Get<T>() where T : PubSubEvent, new()
  {
    var matchingEvent = events.OfType<T>.FirstOrDefault()
    if(matchingEvent != null)
      return matchingEvent;
    matchingEvent = new T();
    events.Add(matchingEvent);
    return matchingEvent;
  }
}

Where the PubSubEvent is something like this:

public abstract class PubSubEvent
{
  private List<Action> actions;
  public void Publish()
  {
    foreach (var action in actions)
      action();
  }
  public void Subscribe(Action action)
  {
    actions.Add(action);
  }
}

In practice, the Get on the event aggregator is static, but I prefer dependency injection if possible. You may not even need the aggregator if you have good dependency injection. The pub sub event is also a bit different, because the PRISM versions take arguments, which is more like what you want. That project is open source, so you may as well look at it.

In any case, an event may be a simple, empty subclass, because the most important thing is the type name. Each event handles it's argument type, and if you want to subscribe to multiple events, you import multiple events.

Using MEF for dependency injection, for instance, you might do something like this:

[ImportingConstructor]
public SomeClass(DatabaseChangedEvent changedEvent)
{
  changedEvent.Subscribe(ReloadData);
}
private void ReloadData()
{
  ...
}

So it's important to think about whether or not you even need all your events to be managed by the same object. It may make far more sense to only have any access at all to the exact events you need. There are many ways to do it. You can easily make the base event class generic, where the generic type is the argument class, and every subclass specifies it specifically. That would be an easy solution.

You generally don't need global things in C#, and static typing can help you quite a bit if you don't try to fight against it.

OTHER TIPS

Let me ask a question : what you happen if in your PHP version I would write:

// PHP
class Sample
{
    public function __construct()
    {
        $dispatcher = new EventDispatcher();
        $dispatcher->addListener("sample.event", array($this, "onEvent"));
        $dispatcher->dispatch   ("sample.event", new WarnEvent()); // oops?
    }

    public function onEvent(ErrorEvent $event)
    {
        echo "Event Dispatched";
    }
}

I'm not experienced with PHP, but this would probably cause some kind of error behavior. In best case, you can handle it, in worst case, you are going to spend days trying to hunt down the error.

C#'s static type system simply doesn't allow to even write code like that for above reasons. If you really don't mind this kind of behavior, you can write:

public static void onEvent(EventArgs args)
{
    if (!(args is ErrorEventArgs))
    {
        // something really wrong just happened
    }
    Console.Out.WriteLine ("Event Dispatched");
}

And about your suggestion of using generics + object. I thinks this might be a good idea. As long as the object is fully encapsulated inside the Dispatcher.

Licensed under: CC-BY-SA with attribution
scroll top