views:

2862

answers:

9

Hello everyone:

I am currently having a hardtime understanding and implementing events in C# using delagates. I am used to the Java way of doing things:

  1. Define an interface for a listener type which would contain a number of method definitions
  2. Define adapter class for that interface to make things easier if I'm not interested in all the events defined in a listener
  3. Define Add, Remove and Get[] methods in the class which raises the events
  4. Define protected fire methods to do the dirty work of looping through the list of added listeners and calling the correct method

This I understand (and like!) - I know I could do this exactly the same in c#, but it seems that a new (better?) system is in place for c#. After reading countless tutorials explaining the use of delegates and events in c# I still am no closer to really understanding what is going on :S


In short, for the following methods how would I implement the event system in c#:

void computerStarted(Computer computer);
void computerStopped(Computer computer);
void computerReset(Computer computer);
void computerError(Computer computer, Exception error);

^ The above methods are taken from a Java application I once made which I'm trying to port over to c#.

Many many thanks!

+5  A: 

You'll have to define a single delegate for that

public delegate void ComputerEvent(object sender, ComputerEventArgs e);

ComputerEventArgs would be defined like this:

public class ComputerEventArgs : EventArgs
{
    // TODO wrap in properties
    public Computer computer;
    public Exception error;

    public ComputerEventArgs(Computer aComputer, Exception anError)
    {
        computer = aComputer;
        error = anError;
    }

    public ComputerEventArgs(Computer aComputer) : this(aComputer, null)
    {
    }
}

The class that fires the events would have these:

public YourClass
{
    ...
    public event ComputerEvent ComputerStarted;
    public event ComputerEvent ComputerStopped;
    public event ComputerEvent ComputerReset;
    public event ComputerEvent ComputerError;
    ...
}

This is how you assign handlers to the events:

YourClass obj = new YourClass();
obj.ComputerStarted += new ComputerEvent(your_computer_started_handler);

Your handler is:

private void ComputerStartedEventHandler(object sender, ComputerEventArgs e)
{
   // do your thing.
}
jop
if you use an Enum for the before/after states then you only need 1 event, ComputerStateChanged
Steven A. Lowe
I prefer having separate events for each event. I've seen code that used Enums for the computer state - but what happens is that the handler will have a switch(e.State) and then call different methods for each state anyways.
jop
You don't have to define your own delegate - that's what EventHandler<TEventArgs> is for.Also the event names are unconventional - event *raising* methods normally start with "On" but the events themselves don't. For example, the Control.Click event is raised by Control.OnClick().
Jon Skeet
@Jon - cool. I am still using C# 2.0 so I haven't tried that yet.
jop
EventHandler<TEventArgs> exists in C# 2.0
Neil Williams
doublecool. I learn new things everyday. :)
jop
+4  A: 

The main difference is that in C# the events are not interface-based. Instead, the event publisher declares the delegate which you can think of as a function pointer (although not exactly the same :-)). The subscriber then implements the event prototype as a regular method and adds a new instance of the delegate to the event handler chain of the publisher. Read more about delegates and events.

You can also read short comparison of C# vs. Java events here.

Franci Penov
Great answer, instead of just giving code you gave him an explanation.
Jonathan C Dickinson
Give a man a fish and he can eat for a day. Teach a man to fish and he can eat for a life. :-)
Franci Penov
+1  A: 

there are several ways to do what you want. The most direct way would be to define delegates for each event in the hosting class, e.g.

public delegate void ComputerStartedDelegate(Computer computer);
protected event ComputerStartedDelegate ComputerStarted;
public void OnComputerStarted(Computer computer)
{
    if (ComputerStarted != null)
    {
        ComputerStarted.Invoke(computer);
    }
}
protected void someMethod()
{
    //...
    computer.Started = true;  //or whatever
    OnComputerStarted(computer);
    //...
}

any object may 'listen' for this event simply by:

Computer comp = new Computer();
comp.ComputerStarted += new ComputerStartedDelegate(
    this.ComputerStartedHandler);

protected void ComputerStartedHandler(Computer computer)
{
    //do something
}

The 'recommended standard way' of doing this would be to define a subclass of EventArgs to hold the Computer (and old/new state and exception) value(s), reducing 4 delegates to one. In this case that would be a cleaner solution, esp. with an Enum for the computer states in case of later expansion. But the basic technique remains the same:

  • the delegate defines the signature/interface for the event handler/listener
  • the event data member is a list of 'listeners'

listeners are removed using the -= syntax instead of +=

Steven A. Lowe
+1  A: 

In c# events are delegates. They behave in a similar way to a function pointer in C/C++ but are actual classes derived from System.Delegate.

In this case, create a custom EventArgs class to pass the Computer object.

public class ComputerEventArgs : EventArgs
{
  private Computer _computer;

  public ComputerEventArgs(Computer computer) {
    _computer = computer;
  }

  public Computer Computer { get { return _computer; } }
}

Then expose the events from the producer:

public class ComputerEventProducer
{
  public event EventHandler<ComputerEventArgs> Started;
  public event EventHandler<ComputerEventArgs> Stopped;
  public event EventHandler<ComputerEventArgs> Reset;
  public event EventHandler<ComputerEventArgs> Error;

  /*
  // Invokes the Started event */
  private void OnStarted(Computer computer) {
    if( Started != null ) {
      Started(this, new ComputerEventArgs(computer));
    }
  }

  // Add OnStopped, OnReset and OnError

}

The consumer of the events then binds a handler function to each event on the consumer.

public class ComputerEventConsumer
{
  public void ComputerEventConsumer(ComputerEventProducer producer) {
    producer.Started += new EventHandler<ComputerEventArgs>(ComputerStarted);
    // Add other event handlers
  }

  private void ComputerStarted(object sender, ComputerEventArgs e) {
  }
}

When the ComputerEventProducer calls OnStarted the Started event is invoked which in turn will call the ComputerEventConsumer.ComputerStarted method.

Andrew Kennan
Events are *not* delegates. Events are something you subscribe for.
Constantin
+15  A: 

You'd create four events, and methods to raise them, along with a new EventArgs-based class to indicate the error:

public class ExceptionEventArgs : EventArgs
{
    private readonly Exception error;

    public ExceptionEventArgs(Exception error)
    {
         this.error = error;
    }

    public Error
    {
         get { return error; }
    }
}

public class Computer
{
    public event EventHandler Started = delegate{};
    public event EventHandler Stopped = delegate{};
    public event EventHandler Reset = delegate{};
    public event EventHandler<ExceptionEventArgs> Error = delegate{};

    protected void OnStarted()
    {
        Started(this, EventArgs.Empty);
    }

    protected void OnStopped()
    {
        Stopped(this, EventArgs.Empty);
    }

    protected void OnReset()
    {
        Reset(this, EventArgs.Empty);
    }

    protected void OnError(Exception e)
    {
        Error(this, new ExceptionEventArgs(e));
    }
}

Classes would then subscribe to the event using either a method or a an anonymous function:

someComputer.Started += StartEventHandler; // A method
someComputer.Stopped += delegate(object o, EventArgs e)
{ 
    Console.WriteLine("{0} has started", o);
};
someComputer.Reset += (o, e) => Console.WriteLine("{0} has been reset");

A few things to note about the above:

  • The OnXXX methods are protected so that derived classes can raise the events. This isn't always necessary - do it as you see fit.
  • The delegate{} piece on each event declaration is just a trick to avoid having to do a null check. It's subscribing a no-op event handler to each event
  • The event declarations are field-like events. What's actually being created is both a variable and an event. Inside the class you see the variable; outside the class you see the event.

See my events/delegates article for much more detail on events.

Jon Skeet
All looks good, but I'm not a fan of the pattern of adding empty delegates to events. I know it means you don't have to check for null, but it means you allocate wasted memory for each class, and you always invoke the callback even if there are no listeners. Checking for null isn't hard... +1 though
Greg Beech
It's a trade-off of a very slight performance hit (really tiny) vs a readability gain. I always aim for readability first, then micro-optimise if I absolutely have to.
Jon Skeet
Premature optimisation isn't my intention, but it seems to me that there's a difference between that and being gratuitously inefficient for little-to-no gain in readability/usability (and in fact I'd argue it's less readable because I'd expect to see the null check in correct event raising code!).
Greg Beech
I'd say you'd only expect to see the null check because you're not used to the pattern - once you know it's there and why, I believe it makes it more readable. That's certainly my experience. In my view the readability benefit is worth the tiny additional memory footprint (continued)
Jon Skeet
unless you know that you'll be creating *lots* of instances. In that case I'd potentially create a "constant" no-op delegate - no memory waste beyond the single instance, and only trivial no-op execution cost. Note that in a multi-threaded class the nullity check needs to be more complex (continued)
Jon Skeet
in order to avoid race conditions - you need to assign to a local variable and compare *that* with null before invoking it, rather than using the member variable twice. Subtleties like that are avoided by what is effectively an implementation of the null object pattern.
Jon Skeet
I don't like empty delegate technique either. This code just feels wrong when I look at it, however it's a matter of taste really. To avoid null-checking I use a custom extension method called `Raise` that does it for me, and often with a generic version of `EventArgs` that has single `Item` property.
gaearon
+2  A: 

First of all, there is a standard method signature in .Net that is typically used for events. The languages allow any sort of method signature at all to be used for events, and there are some experts who believe the convention is flawed (I mostly agree), but it is what it is and I will follow it for this example.

  1. Create a class that will contain the event’s parameters (derived from EventArgs).
public class ComputerEventArgs : EventArgs 
{
  Computer computer; 
  // constructor, properties, etc.
}
  1. Create a public event on the class that is to fire the event.
    class ComputerEventGenerator  // I picked a terrible name BTW.
    {
      public event EventHandler<ComputerEventArgs> ComputerStarted;
      public event EventHandler<ComputerEventArgs> ComputerStopped;
      public event EventHandler<ComputerEventArgs> ComputerReset;
    ...
    }
  1. Call the events.
    class ComputerEventGenerator
    {
    ...
      private void OnComputerStarted(Computer computer) 
      {
        EventHandler<ComputerEventArgs> temp = ComputerStarted;
        if (temp != null) temp(this, new ComputerEventArgs(computer)); // replace "this" with null if the event is static
      }
     }
  1. Attach a handler for the event.
    void OnLoad()
    {
      ComputerEventGenerator computerEventGenerator = new ComputerEventGenerator();
      computerEventGenerator.ComputerStarted += new  EventHandler<ComputerEventArgs>(ComputerEventGenerator_ComputerStarted);
    }
  1. Create the handler you just attached (mostly by pressing the Tab key in VS).
    private void ComputerEventGenerator_ComputerStarted(object sender, ComputerEventArgs args)
    {
      if (args.Computer.Name == "HAL9000")
         ShutItDownNow(args.Computer);
    }
  1. Don't forget to detach the handler when you're done. (Forgetting to do this is the biggest source of memory leaks in C#!)
    void OnClose()
    {
      ComputerEventGenerator.ComputerStarted -= ComputerEventGenerator_ComputerStarted;
    }

And that's it!

EDIT: I honestly can't figure out why my numbered points all appear as "1." I hate computers.

Jeffrey L Whitledge
I don't know why the editor is giving me so much trouble. At first the code blocks wouldn't do anything but indent, so I had to change it to pre tags. Then my carefully numbered sequence is now all 1s. This thing is making me look ridiculous!
Jeffrey L Whitledge
Jeffrey: It is because numbered list items need to be in consecutive order. You're alternating code and lists which are two seperate types of blocks.
Spoike
But I didn't encode the list as a list, I just typed in the numbers themselves. It sees where I typed "3." figures "oh, that's a numbered item. I'll just change that to a 1." Computers!
Jeffrey L Whitledge
+1  A: 

The delegate declares a function signature, and when it's used as an event on a class it also acts as a collection of enlisted call targets. The += and -= syntax on an event is used to adding a target to the list.

Given the following delegates used as events:

// arguments for events
public class ComputerEventArgs : EventArgs
{
    public Computer Computer { get; set; }
}

public class ComputerErrorEventArgs : ComputerEventArgs
{
    public Exception Error  { get; set; }
}

// delegates for events
public delegate void ComputerEventHandler(object sender, ComputerEventArgs e);

public delegate void ComputerErrorEventHandler(object sender, ComputerErrorEventArgs e);

// component that raises events
public class Thing
{
    public event ComputerEventHandler Started;
    public event ComputerEventHandler Stopped;
    public event ComputerEventHandler Reset;
    public event ComputerErrorEventHandler Error;
}

You would subscribe to those events with the following:

class Program
{
    static void Main(string[] args)
    {
        var thing = new Thing();
        thing.Started += thing_Started;
    }

    static void thing_Started(object sender, ComputerEventArgs e)
    {
        throw new NotImplementedException();
    }
}

Although the arguments could be anything, the object sender and EventArgs e is a convention that's used very consistently. The += thing_started will first create an instance of the delegate pointing to target method, then add it to the event.

On the component itself you would typically add methods to fire the events:

public class Thing
{
    public event ComputerEventHandler Started;

    public void OnStarted(Computer computer)
    {
        if (Started != null)
            Started(this, new ComputerEventArgs {Computer = computer});
    }
}

You must test for null in case no delegates have been added to the event. When you make the method call however all delegates which have been added will be called. This is why for events the return type is void - there is no single return value - so to feed back information you would have properties on the EventArgs which the event handlers would alter.

Another refinement would be to use the generic EventHandler delegate rather than declaring a concrete delegate for each type of args.

public class Thing
{
    public event EventHandler<ComputerEventArgs> Started;
    public event EventHandler<ComputerEventArgs> Stopped;
    public event EventHandler<ComputerEventArgs> Reset;
    public event EventHandler<ComputerErrorEventArgs> Error;
}
loudej
A: 

Thank you all so much for your answers! Finally I'm starting to understand what is going on. Just one thing; It seems that if each event had a different number/type of arguments I'd need to create a different :: EventArgs class to deal with it:

public void computerStarted(Computer computer);
public void computerStopped(Computer computer);
public void computerReset(Computer computer);
public void breakPointHit(Computer computer, int breakpoint);
public void computerError(Computer computer, Exception exception);

This would require three classses to deal with the events!? (Well two custom, and one using the default EventArgs.Empty class)

Cheers!

Richie_W
As I said in my answer, some experts believe the .Net convetion for events is flawed. You just highlighted one of the major objections. (Another being the "object sender" convention, which is unnecessary and breaks encapsulation in many cases.) The EventArgs thing works OK in practice, usually.
Jeffrey L Whitledge
By the way, I expect you to read every word of every answer before you pick a winner! ;-P
Jeffrey L Whitledge
Haha, I've upvoted everyone - all good answers. As for which one is the 'winner'...argh!
Richie_W
A: 

Ok, FINAL clarification!: So this is pretty much the best I can do code-wise to implement those events?

   public class Computer {

        public event EventHandler Started;

        public event EventHandler Stopped;

        public event EventHandler Reset;

        public event EventHandler<BreakPointEvent> BreakPointHit;

        public event EventHandler<ExceptionEvent> Error;

        public Computer() {
            Started = delegate { };
            Stopped = delegate { };
            Reset = delegate { };
            BreakPointHit = delegate { };
            Error = delegate { };
        }

        protected void OnStarted() {
            Started(this, EventArgs.Empty);
        }

        protected void OnStopped() {
            Stopped(this, EventArgs.Empty);
        }

        protected void OnReset() {
            Reset(this, EventArgs.Empty);
        }

        protected void OnBreakPointHit(int breakPoint) {
            BreakPointHit(this, new BreakPointEvent(breakPoint));
        }

        protected void OnError(System.Exception exception) {
            Error(this, new ExceptionEvent(exception));
        }
    }
}
Richie_W