Domanda

Quindi sono bloccato nel fissare / mantenere un altro codice programmatore (blech)

Sono un fermo professore della regola " Se non è rotto, non aggiustarlo! " così depsite che voglio cambiare qualcosa ogni volta che trovo codice orrendo, mi sto mantenendo limitato a modificare solo la quantità minima assoluta di codice possibile per apportare le correzioni richieste. Ma in alcuni casi ho davvero bisogno di capire qualcosa prima di provare a seguirlo / cambiarlo.

Mi sono imbattuto in questo piccolo qui:

region.LineSelected = (x) => { };

E mi chiedo se sia uguale a questo:

region.LineSelected = null;

Voglio essere sicuro al 100% di quello che sta facendo la prima riga prima di andare a cambiare il metodo in cui si trova.

È stato utile?

Soluzione

Modifica in base alla mia attuale opinione sull'argomento

Non sono gli stessi. La versione lambda aggiunge un gestore eventi a un metodo anonimo vuoto. Ciò consente ad altro codice di aumentare liberamente LineSelected () senza preoccuparsi del fatto che sia nullo (ovvero non abbia ascoltatori).

Eg.

var lineSelected = this.LineSelected;

if (lineSelected != null)
{
    lineSelected(EventArgs.Empty);
}

L'istruzione precedente può generare un'eccezione NullReferenceException se qualcosa annulla l'iscrizione a LineSelected in un altro thread dopo il se ma prima che venga generato l'evento. Assegnazione di LineSelected a una variabile temporanea e quindi rilancio che può chiamare un listener di eventi non sottoscritto. L'assegnazione del gestore di eventi a una variabile locale è il metodo consigliato per gestire i delegati null.

Aggiungendo un delegato vuoto, un altro codice è sempre in grado di chiamare LineSelected senza timore di una NullReferenceException. Assegnando i delegati dell'evento multicast a una variabile locale, puoi essere sicuro che il valore non può essere modificato da un altro thread.

Altri suggerimenti

Non riesco a pensare a nessun motivo per avere quella prima riga di codice. L'unica cosa che mi viene in mente è quando si genera l'evento LineSelected, se si dispone della prima riga di codice nella classe, non è necessario verificare se l'evento LineSelected è null. vale a dire:

if (this.LineSelected != null)
{
   LineSelected(this,new EventArgs());
}

Invece, puoi semplicemente generare l'evento senza controlli null.

Tuttavia, con la seconda riga di codice, dovrai verificare la presenza di null.

Questo non è un gestore di eventi, è un semplice delegato. (Un gestore di eventi dovrebbe essere modificato con + = e - = per collegare e scollegare l'evento).

Come è stato precedentemente commentato, l'impostazione della proprietà del delegato su un gestore vuoto significa che non è necessario eseguire controlli null prima di invocare il delegato (presupponendo che nient'altro possa impostarlo su null).

Questo può rendere più conveniente il codice chiamante, ma non deve essere confuso come un miglioramento delle prestazioni (rimuovendo la necessità di verificare la presenza di null). Generalmente l'overhead di una chiamata del metodo delegato sarà significativamente superiore a quello del controllo null. Potrebbero esserci alcuni scenari (ad esempio se il delegato ha un'implementazione "reale" del 99,99% delle volte) in cui evitare il controllo nullo potrebbe migliorare le prestazioni, ma è difficile immaginare uno scenario in cui la minima differenza di prestazioni potrebbe essere abbastanza importante vale la pena che non giustificherebbe anche la rimozione totale dell'invocazione dei delegati a favore di qualcosa di più efficiente.

Penso che questa sia una tecnica per evitare controlli nulli su ogni evento.

Se il codice di raccolta eventi LineSelected non ha un controllo null corretto, ciò causerà un'eccezione:

region.LineSelected = null;

/* no event handlers added to LineSelected */

class Region {
    void OnLineSelected() {
        // Null error!
        LineSelected();
    }
}

Tuttavia, se è stato aggiunto un gestore di effetti senza effetti vuoti, il codice sopra funzionerà perfettamente, anche se nessuno aggiunge un gestore all'evento, perché ci sarà sempre quel gestore vuoto collegato.

Per espandere ciò che hanno detto Richard e Andrew, è l'equivalente di

region.LineSelected = delegate {};

Il che significa che quando si genera l'evento, non è necessario controllare prima null, perché ha un delegato (al prezzo di un piccolo hit delle prestazioni)

No, non è la stessa cosa: la prima riga assegna a LineSelected un delegato vuoto che è molto diverso da null .

Il modo più semplice per individuare la differenza è guardare il codice che il compilatore genera per tuo conto quando usi la sintassi lambda. Questo codice:

using System;

class Program
{
    static void Main()
    {
        Action<int> func0 = (x) => { };
        Action<int> func1 = null;
    }
}

Compila davvero per questo:

internal class Program
{
    // Methods
    private static void Main()
    {
        Action<int> func0 = delegate (int x) {
        };
    }
}

Si noti che il compilatore è stato abbastanza intelligente da rimuovere func1 dato che era impostato su null e non referenziato altrove. Tuttavia, func0 rimane e viene impostato su un delegato che, sebbene non faccia nulla ma è molto diverso da null .

Non sono gli stessi, perché il gestore eventi è impostato.

Diciamo che la classe che espone LineSelected, ha dimenticato di:

if( LineSelected != null )
    LineSelected(...)

Se quella classe dovesse chiamare LineSelected e nessuno è in ascolto, genererà NullReferenceException

Nota che puoi anche fare (all'interno della regione) per evitare le condizioni di gara:

var event = LineSelected;     if (evento! = null)         evento (...

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top