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:55

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.

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

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.

查看更多
甜甜的少女心
4楼-- · 2019-01-31 10:59

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