C# event handling (compared to Java)

2019-01-31 10:07发布

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!

9条回答
倾城 Initia
2楼-- · 2019-01-31 10:35

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!

查看更多
贪生不怕死
3楼-- · 2019-01-31 10:35

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));
        }
    }
}
查看更多
SAY GOODBYE
4楼-- · 2019-01-31 10:49

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.

查看更多
爷的心禁止访问
5楼-- · 2019-01-31 10:50

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.

查看更多
Lonely孤独者°
6楼-- · 2019-01-31 10:50

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 +=

查看更多
倾城 Initia
7楼-- · 2019-01-31 10:52

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.
}
查看更多
登录 后发表回答