Obsługa zdarzeń C# (w porównaniu do Javy)

Obecnie ciężko mi zrozumieć i zaimplementować zdarzenia w C# używając delagates. Jestem przyzwyczajony do Java sposób robienia rzeczy:

  1. Zdefiniuj interfejs dla typu słuchacza, który będzie zawierał wiele definicji metod
  2. Zdefiniuj klasę adaptera dla tego interfejsu, aby było łatwiej, jeśli nie interesują mnie wszystkie zdarzenia zdefiniowane w słuchaczu
  3. Zdefiniuj metody Add, Remove i Get[] w klasie, która podnosi Wydarzenia
  4. Zdefiniuj metody ochrony przeciwpożarowej, aby wykonać brudną robotę poprzez zapętlenie listy dodanych słuchaczy i wywołanie prawidłowej metody
To Rozumiem (i lubię!)- Wiem, że mógłbym to zrobić dokładnie tak samo w c#, ale wydaje się, że nowy (lepszy?) system jest na miejscu dla c#. Po przeczytaniu niezliczonych samouczków wyjaśniających użycie delegatów i wydarzeń w c# nadal nie jestem bliżej do naprawdę zrozumienia, co się dzieje: S

W skrócie, dla następujących metody jak zaimplementowałbym system zdarzeń w c#:

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

^ powyższe metody są pobrane z aplikacji Java, którą kiedyś stworzyłem, którą próbuję przenieść do c#.

Wielkie dzięki!

Author: Richard Walton, 2008-10-08

9 answers

Można utworzyć cztery zdarzenia i metody ich wywoływania, wraz z nową klasą opartą na EventArgs, aby wskazać błąd:

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));
    }
}

Klasy zapisują się do zdarzenia za pomocą metody lub funkcji anonimowej:

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");

Kilka rzeczy na temat powyższego:

  • metody OnXXX są chronione tak, że klasy pochodne mogą wywoływać zdarzenia. Nie zawsze jest to konieczne - rób to, co uważasz za stosowne.
  • fragment delegate{} na każdej deklaracji zdarzenia jest po prostu sztuczka, aby uniknąć konieczności wykonywania kontroli zerowej. To subskrybowanie obsługi zdarzeń no-op do każdego zdarzenia
  • deklaracje zdarzeń są zdarzeniami polowymi . To, co jest faktycznie tworzone, to zarówno zmienna , jak i Zdarzenie. Wewnątrz klasy widzisz zmienną; poza klasą widzisz Zdarzenie.

Zobacz mój artykuł wydarzenia / delegaci aby uzyskać więcej szczegółów na temat wydarzeń.

 17
Author: Jon Skeet,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-10-08 05:29:53

Będziesz musiał zdefiniować dla tego jednego delegata

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

ComputerEventArgs definiuje się następująco:

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)
    {
    }
}

Klasa, która odpala zdarzenia będzie miała następujące:

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

W ten sposób przypisujesz obsługę zdarzeń:

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

Twój opiekun to:

private void ComputerStartedEventHandler(object sender, ComputerEventArgs e)
{
   // do your thing.
}
 5
Author: jop,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-10-08 15:31:08

Główna różnica polega na tym, że w C# zdarzenia nie są oparte na interfejsie. Zamiast tego wydawca zdarzenia deklaruje delegata, który można traktować jako wskaźnik funkcji (choć nie dokładnie taki sam: -)). Następnie subskrybent implementuje prototyp zdarzenia jako zwykłą metodę i dodaje nową instancję delegata do łańcucha obsługi zdarzenia wydawcy. Przeczytaj więcej o delegaci i Wydarzenia .

Możesz również przeczytać krótkie porównanie zdarzeń C# vs. Java tutaj .

 4
Author: Franci Penov,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-10-08 05:22:32

Po pierwsze, istnieje standardowa sygnatura metody w. Net, która jest zwykle używana dla zdarzeń. Języki pozwalają na stosowanie wszelkiego rodzaju podpisów metod w ogóle w przypadku zdarzeń, a niektórzy eksperci uważają, że konwencja jest wadliwa( w większości się Zgadzam), ale jest jak jest i podążę za nim na tym przykładzie.

  1. Utwórz klasę, która będzie zawierać parametry zdarzenia (pochodzące z EventArgs).
public class ComputerEventArgs : EventArgs 
{
  Computer computer; 
  // constructor, properties, etc.
}
  1. Utwórz publiczne Zdarzenie na klasie, które ma odpal imprezę.
    class ComputerEventGenerator  // I picked a terrible name BTW.
    {
      public event EventHandler<ComputerEventArgs> ComputerStarted;
      public event EventHandler<ComputerEventArgs> ComputerStopped;
      public event EventHandler<ComputerEventArgs> ComputerReset;
    ...
    }
  1. zadzwoń do wydarzeń.
    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. Dołącz obsługę zdarzenia.
    void OnLoad()
    {
      ComputerEventGenerator computerEventGenerator = new ComputerEventGenerator();
      computerEventGenerator.ComputerStarted += new  EventHandler<ComputerEventArgs>(ComputerEventGenerator_ComputerStarted);
    }
  1. Utwórz program obsługi, który właśnie dołączyłeś(głównie naciskając klawisz Tab w VS).
    private void ComputerEventGenerator_ComputerStarted(object sender, ComputerEventArgs args)
    {
      if (args.Computer.Name == "HAL9000")
         ShutItDownNow(args.Computer);
    }
    Nie zapomnij odłączyć opiekuna, kiedy skończysz. (Zapomnienie o tym jest największym źródłem wycieków pamięci w C#!)
    void OnClose()
    {
      ComputerEventGenerator.ComputerStarted -= ComputerEventGenerator_ComputerStarted;
    }

I to wszystko!

EDIT: szczerze Nie wiem dlaczego moje numerowane punkty wszystkie pojawiają się jako " 1."Nienawidzę komputerów.

 2
Author: Jeffrey L Whitledge,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-10-08 05:52:34

Istnieje kilka sposobów, aby zrobić to, co chcesz. najbardziej bezpośrednim sposobem byłoby zdefiniowanie delegatów dla każdego wydarzenia w klasie hostingowej, np.

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);
    //...
}

Każdy obiekt może "nasłuchać" tego zdarzenia po prostu przez:

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

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

'zalecanym standardowym sposobem' jest zdefiniowanie podklasy EventArgs do przechowywania wartości komputera(oraz starego/nowego stanu i wyjątku), redukując 4 delegatów do jednego. W tym przypadku byłoby to czystsze rozwiązanie, esp. z Enum dla Stany komputera w przypadku późniejszej rozbudowy. Ale podstawowa technika pozostaje taka sama:

  • delegat definiuje sygnaturę / interfejs dla programu obsługi zdarzeń / słuchacza
  • Dane zdarzenia to lista "słuchaczy"

Słuchacze są usuwani za pomocą składni -= zamiast + =

 1
Author: Steven A. Lowe,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-10-08 05:23:59

W C# zdarzenia są delegatami. Zachowują się w podobny sposób jak wskaźnik funkcji w C / C++, ale są rzeczywistymi klasami wywodzącymi się z systemu.Delegat.

W tym przypadku utwórz własną klasę EventArgs, aby przekazać obiekt komputerowy.

public class ComputerEventArgs : EventArgs
{
  private Computer _computer;

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

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

Następnie ujawnić wydarzenia od producenta:

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

}

Konsument zdarzeń następnie wiąże funkcję obsługi do każdego zdarzenia na konsumencie.

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) {
  }
}

Gdy ComputerEventProducer wywoła OnStarted, wywołane jest rozpoczęte Zdarzenie który z kolei wywoła ComputerEventConsumer.Metoda ComputerStarted.

 1
Author: Andrew Kennan,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-10-08 05:24:52

Delegat deklaruje podpis funkcji, a gdy jest używany jako zdarzenie na klasie, działa również jako zbiór celów wywołania. Składnia + = i - = w zdarzeniu służy do dodania obiektu docelowego do listy.

Biorąc pod uwagę następujące delegaty używane jako Wydarzenia:

// 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;
}

Subskrybujesz te wydarzenia z następującymi wpisami:

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();
    }
}

Chociaż argumenty mogą być dowolne, object sender i EventArgs e jest konwencją, która jest używana bardzo konsekwentnie. Na + = thing_started najpierw utworzy instancję delegata wskazującego na metodę target, a następnie doda ją do zdarzenia.

Na samym komponencie zazwyczaj dodawane są metody odpalania zdarzeń:

public class Thing
{
    public event ComputerEventHandler Started;

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

Musisz przetestować na null, jeśli do zdarzenia nie dodano delegatów. Po wywołaniu metody zostaną wywołane wszystkie delegaty, które zostały dodane. Z tego powodu dla zdarzeń typ zwracany jest void - nie ma pojedynczej wartości zwracanej - tak więc do przekazywania informacji będziesz miał właściwości na EventArgs, które manipulatory zdarzeń będą zmieniać.

Innym udoskonaleniem byłoby użycie generycznego delegata EventHandler zamiast deklarowania konkretnego delegata dla każdego typu args.

public class Thing
{
    public event EventHandler<ComputerEventArgs> Started;
    public event EventHandler<ComputerEventArgs> Stopped;
    public event EventHandler<ComputerEventArgs> Reset;
    public event EventHandler<ComputerErrorEventArgs> Error;
}
 0
Author: loudej,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-10-08 05:47:34

Dziękuję wszystkim za odpowiedzi! W końcu zaczynam rozumieć, o co chodzi. Tylko jedno; wydaje się, że gdyby każde zdarzenie miało inną liczbę / rodzaj argumentów, musiałbym utworzyć inną klasę:: EventArgs, aby sobie z tym poradzić:

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);

To wymagałoby trzech klas, aby poradzić sobie z wydarzeniami!? (Dobrze dwa niestandardowe, a jeden za pomocą domyślnych EventArgs.Empty class)

Zdrówko!
 0
Author: Richard Walton,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-10-08 14:44:31

Ok, Ostatnie Wyjaśnienie!: Czyli to chyba najlepsze, co mogę zrobić, żeby zaimplementować te zdarzenia?

   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));
        }
    }
}
 0
Author: Richard Walton,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-10-08 16:36:56