2010-06-23 15 views

risposta

18

E 'sicuramente un punto di fastidio.

Quando si scrive codice che accede a un evento di campo simile all'interno di una classe, si è in realtà accesso al campo stesso (Modulo alcuni cambiamenti in C# 4; non andiamo lì per il momento).

Quindi, le opzioni sarebbero:

  • special-case invocazioni di eventi sul campo in questo modo che essi in realtà non si riferiscono al campo direttamente, ma invece aggiunto un involucro
  • maniglia tutto delegare le invocazioni in modo diverso, in modo che:

    Action<string> x = null; 
    x(); 
    

    non genera un'eccezione.

Naturalmente, per i delegati non nulle (ed eventi) entrambe le opzioni sollevano un problema:

Func<int> x = null; 
int y = x(); 

caso che silenziosamente restituire 0? (Il valore predefinito di un int.) O è in realtà mascherare un bug (più probabile). Sarebbe alquanto incoerente ignorare silenziosamente il fatto che si stia tentando di richiamare un delegato nullo. Sarebbe ancora più strano in questo caso, che non usa lo zucchero sintattico C# 's:

Func<int> x = null; 
int y = x.Invoke(); 

Fondamentalmente le cose diventano difficili e in contrasto con il resto del linguaggio quasi qualunque cosa tu faccia. Non mi piace neanche, ma non sono sicuro di quale soluzione pratica ma coerente potrebbe essere ...

+4

Abbastanza francamente lo vedo come un disturbo * molto * minore. Preferisco di gran lunga un'eccezione rispetto agli errori silenziosi. – ChaosPandion

+0

Sebbene, a volte quando vedo * "frameworks" * lanciando un sacco di 'NullReferenceExceptions' la mia risoluzione si indebolisce. – ChaosPandion

+0

Le incoerenze abbondano. Non riesco ancora a chiamare 'Nullable .HasValue' senza pensare" non puoi accedere a una proprietà su un valore che potrebbe essere nullo! " –

3

in realtà non so il motivo per cui è questo fatto, ma c'è una variazione di un Null Object pattern specificamente per i delegati:

private event EventHandler Foo = (sender, args) => {}; 

In questo modo si può liberamente richiamare Foo senza mai controllare per null.

+2

'evento pubblico EventHandler Foo = delegato {};' è più pulita IMO. Anche se personalmente non mi piace questo schema. –

3

Perché RaiseEvent comporta un sovraccarico.

C'è sempre un compromesso tra controllo e facilità d'uso.

  • VB.Net: facilità d'uso,
  • C#: un maggiore controllo come VB
  • C++: un controllo ancora maggiore, meno di guida, più facile da spararsi in un piede
+0

Quali spese generali? Verifica di 'null'? Questo è sicuramente molto meno di una chiamata al metodo ... –

+0

Stavo pensando alla sintassi di Handles di VB.Net. Ma hai ragione, non molto, modificato. – GvS

12

di solito ovviare a questo dichiarando nostri eventi come questo:

public event EventHandler<FooEventArgs> Foo = delegate { }; 

questo ha due vantaggi. Il primo è che non abbiamo il controllo per null. Il secondo è che si evita il problema sezione critica che è onnipresente in tipico cottura evento:

// old, busted code that happens to work most of the time since 
// a lot of code never unsubscribes from events 
protected virtual void OnFoo(FooEventArgs e) 
{ 
    // two threads, first checks for null and succeeds and enters 
    if (Foo != null) { 
     // second thread removes event handler now, leaving Foo = null 
     // first thread resumes and crashes. 
     Foo(this, e); 
    } 
} 

// proper thread-safe code 
protected virtual void OnFoo(FooEventArgs e) 
{ 
    EventHandler<FooEventArgs> handler = Foo; 
    if (handler != null) 
     handler(this, e); 
} 

Ma con l'inizializzazione automatica Foo un delegato vuoto, non c'è mai verifiche necessarie e il codice viene automaticamente filettatura sicuro e più facile da leggere al boot: "il modo migliore per evitare di nulla non è avere uno"

protected virtual void OnFoo(FooEventArgs e) 
{ 
    Foo(this, e); // always good 
} 

Con tante scuse a Pat Morita nel Karate Kid,

Per quanto riguarda il perché, C# non ti coccola tanto quanto VB. Sebbene la parola chiave evento nasconda la maggior parte dei dettagli di implementazione di multicast delegates, ti dà un controllo migliore di VB.

+0

+1: O in modo più convincente se si inizia l'evento a un delegato che fa qualcosa di pratico (se lo scenario lo richiede ovviamente), non avresti mai bisogno di fare il controllo. Un buon punto per evitare le condizioni della gara negli scenari di threading. Questa è la migliore risposta a mio parere ... anche migliore della mia :) –

+0

È tutto divertimento e giochi finché non provi a convertire quanto sopra in Visual Basic, e trovo che non supporta i delegati anonimi ...! – NibblyPig

+1

@SLC: Questo è meno un problema per Visual Basic, poiché la parola chiave RaiseEvent gestisce correttamente il controllo 'null'. –

0

Il motivo si riduce a C# che offre maggiore controllo. In C# non è necessario eseguire il controllo null se lo si desidera. Nel seguente codice MyEvent non può mai essere null quindi perché preoccuparsi di fare il controllo?

public class EventTest 
{ 
    private event EventHandler MyEvent = delegate { Console.WriteLine("Hello World"); } 

    public void Test() 
    { 
    MyEvent(this, new EventArgs()); 
    } 
} 
+0

C'è qualche motivo per cui non avrebbero potuto fare * entrambi *? Mi sembra una PITA abbastanza comune che sono sorpreso che non ci sia una sintassi alternativa che eviti il ​​controllo nullo esplicito. –

8

È necessario considerare ciò che il codice sarebbe richiesto se impostare l'impianto idraulico per generare l'evento in primo luogo sarebbe costoso (come SystemEvents) o durante la preparazione degli argomenti evento sarebbe costoso (come l'evento Paint).

Lo stile di gestione di eventi di Visual Basic non consente di posticipare il costo del supporto di tale evento. Non è possibile ignorare gli add-on/remove accessors per ritardare la messa in opera dei costosi impianti idraulici. E non puoi scoprire che potrebbero non esserci gestori di eventi abbonati in modo che bruciando i cicli per preparare gli argomenti dell'evento sia una perdita di tempo.

Non è un problema in C#. Tradizione classica tra comodità e controllo.

+0

+1 - non posso credere di non averlo notato quando l'ho chiesto a lol. –

5

I metodi di estensione forniscono un modo molto interessante per aggirare questo problema. Si consideri il seguente codice:

static public class Extensions 
{ 
    public static void Raise(this EventHandler handler, object sender) 
    { 
     Raise(handler, sender, EventArgs.Empty); 
    } 

    public static void Raise(this EventHandler handler, object sender, EventArgs args) 
    { 
     if (handler != null) handler(sender, args); 
    } 

    public static void Raise<T>(this EventHandler<T> handler, object sender, T args) 
     where T : EventArgs 
    { 
     if (handler != null) handler(sender, args); 
    } 
} 

Ora si può semplicemente fare questo:

class Test 
{ 
    public event EventHandler SomeEvent; 

    public void DoSomething() 
    { 
     SomeEvent.Raise(this); 
    } 
} 

Tuttavia, come altri già citati, si dovrebbe essere a conoscenza del possibile condizione di competizione in scenari multi-threaded.

+0

È pulito, ma è necessario copiare l'evento prima di sollevarlo. (Vedi il commento di Eric Lippert alcuni post in alto ...) –

+0

Penso che Eric Lippert si riferisca a un'altra condizione di competizione che potrebbe ancora verificarsi, anche con la copia locale. Ho trovato un post su di lui su quel numero: http://blogs.msdn.com/b/ericlippert/archive/2009/04/29/events-and-races.aspx Il problema è che thread- gli eventi sicuri richiedono un'attenta considerazione e sebbene la copia locale risolva uno dei problemi, non li risolverà tutti. Rendendolo thread-safe richiederà un codice aggiuntivo intorno all'evento reale. Non volevo dare l'impressione che il codice sia sicuro per i thread, quindi l'ultima frase. – ollb

1

Modifica: Come l'OP sottolinea, questa risposta non affronta il corpo della domanda. Tuttavia, alcuni potrebbero trovare utile perché fornisce una risposta per il titolo della domanda (se preso da solo):

Perché C# richiede di scrivere un controllo Null ogni volta che si attiva un evento?

Fornisce anche un contesto per l'intento del corpo della domanda che alcuni potrebbero trovare utile. Quindi, per questi motivi e su this advice su Meta, lascerò questa risposta in piedi.


Testo originale:

Nel suo articolo di MSDN How to: Publish Events that Conform to .NET Framework Guidelines (C# Programming Guide) (Visual Studio 2013), Microsoft include il seguente commento nel suo esempio:

// Make a temporary copy of the event to avoid possibility of 
// a race condition if the last subscriber unsubscribes 
// immediately after the null check and before the event is raised. 

Ecco un estratto più grande dal codice di esempio di Microsoft che dà contesto a quel commento.

// Wrap event invocations inside a protected virtual method 
// to allow derived classes to override the event invocation behavior 
protected virtual void OnRaiseCustomEvent(CustomEventArgs e) 
{ 
    // Make a temporary copy of the event to avoid possibility of 
    // a race condition if the last subscriber unsubscribes 
    // immediately after the null check and before the event is raised. 
    EventHandler<CustomEventArgs> handler = RaiseCustomEvent; 

    // Event will be null if there are no subscribers 
    if (handler != null) 
    { 
     // Format the string to send inside the CustomEventArgs parameter 
     e.Message += String.Format(" at {0}", DateTime.Now.ToString()); 

     // Use the() operator to raise the event. 
     handler(this, e); 
    } 
} 
+0

La domanda non è "Perché questo schema è necessario?"; la domanda è "Perché C# ti fa scrivere e non incapsularlo all'interno dell'uso' operator() 'dell'evento?" –

0

Nota che a partire dal C# 6, il linguaggio offre ora una sintassi concisa per eseguire questo controllo nullo convenientemente. Es .:

public event EventHandler SomeEvent; 

private void M() 
{ 
    // raise the event: 
    SomeEvent?.Invoke(this, EventArgs.Empty); 
} 

Vedi Null Conditional Operator

Problemi correlati