3

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);
        }
    }
}
  • Off topic: I noticed you deleted your previous question. It wasn't a bad question, and it would be perfectly on topic on our sister site, Code Review Stack Exchange. Would you mind if I undeleted it and send it over there? – yannis Dec 16 '14 at 9:53
  • @YannisRizos I wasn't sure whether to post this question here or on StackOverflow which is where I usually answer PHP related questions. My previous deleted question if I recall was related to my confusion over the amount of work required for simple operations in C, however I have since learned that unless I use third party tools, it is what I should expect. However if you think the question was useful, then sure, undelete it. – Flosculus Dec 16 '14 at 10:04
  • 1
    just a note TryGetValue (and in general all the Try method with an out paramerter) return a bool which is true if the value was found. So you can remove the checks for listener == null and just check whether the TryGetValue actually retrieve something. – Fabio Marcolini Dec 16 '14 at 10:14
  • 1
    @FabioMarcolini I can't because I would have to define the class with the same generic. I am trying to get it to handle a variety of different events. – Flosculus Dec 16 '14 at 12:01
  • 1
    Looking closely at this, I don't think you'd want to do it this way at all in C#. It reminds me somewhat of Prism's EventAggregation, but there you'd be using subclasses to handle the subscriptions, and if no instance is found, one is created. I'd suggest looking into it. The main issue is that C# and PHP require very different perspectives. Porting code from one to the other often makes very little sense, because the implementations will be working around weaknesses the other language does not have, and lacking the strengths. I'd suggest trying to solve a new problem yourself to learn. – Magus Dec 16 '14 at 18:41
1

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.

2

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.

  • In your PHP case it would throw an InvalidArgumentException, which is OK (sort of) in PHP because this can be caught, although in this context you wouldn't expect to need to try catch on every event dispatch. Your case with C# is introspection, something I would prefer to avoid having to use on every handler. I like that you consider my suggestion of generics + object suitable, my only concern is the casting involved. I have updated my answer with the refactored class. – Flosculus Dec 16 '14 at 11:05

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.