In C #, cosa succede quando si chiama un metodo di estensione su un oggetto null?


329

Il metodo viene chiamato con un valore null o fornisce un'eccezione di riferimento null?

MyObject myObject = null;
myObject.MyExtensionMethod(); // <-- is this a null reference exception?

In questo caso non dovrò mai controllare il mio parametro 'this' per null?


A meno che, ovviamente, tu abbia a che fare con ASP.NET MVC che genererà questo errore Cannot perform runtime binding on a null reference.
Mrchief,

Risposte:


387

Funzionerà benissimo (nessuna eccezione). I metodi di estensione non utilizzano chiamate virtuali (ovvero utilizza l'istruzione "call" il, non "callvirt"), pertanto non esiste un controllo null a meno che non venga scritto da solo nel metodo di estensione. Questo è effettivamente utile in alcuni casi:

public static bool IsNullOrEmpty(this string value)
{
    return string.IsNullOrEmpty(value);
}
public static void ThrowIfNull<T>(this T obj, string parameterName)
        where T : class
{
    if(obj == null) throw new ArgumentNullException(parameterName);
}

eccetera

Fondamentalmente, le chiamate alle chiamate statiche sono molto letterali, vale a dire

string s = ...
if(s.IsNullOrEmpty()) {...}

diventa:

string s = ...
if(YourExtensionClass.IsNullOrEmpty(s)) {...}

dove ovviamente non c'è nessun controllo nullo.


1
Marc, stai parlando di chiamate "virtuali", ma lo stesso vale per le chiamate non virtuali sui metodi di istanza. Penso che la parola "virtuale" qui sia fuori luogo.
Konrad Rudolph,

6
@Konrad: dipende dal contesto. Il compilatore C # di solito usa callvirt anche per metodi non virtuali, proprio per ottenere un controllo nullo.
Jon Skeet,

Mi riferivo alla differenza tra le istruzioni call e callvirt. In una modifica ho effettivamente cercato di nascondere le due pagine di Opcodes, ma l'editore ha scoperto i link ...
Marc Gravell

2
Non vedo come questo uso dei metodi di estensione possa essere davvero utile. Solo perché si può fare non significa che sia giusto, e come ha detto Binary Worrier sotto, mi sembra più un'aberrazione per non dire altro.
Trap

3
@Trap: questa funzionalità è eccezionale se ti piacciono le programmazioni in stile funzionale.
Roy Tinker,

50

Aggiunta alla risposta corretta di Marc Gravell.

È possibile che venga visualizzato un avviso dal compilatore se è ovvio che questo argomento è null:

default(string).MyExtension();

Funziona bene in fase di esecuzione, ma produce l'avviso "Expression will always cause a System.NullReferenceException, because the default value of string is null".


32
Perché dovrebbe avvisare "causa sempre un System.NullReferenceException". Quando in realtà non lo farà mai?
Potenza

46
Fortunatamente, noi programmatori ci preoccupiamo solo degli errori, non degli avvertimenti: p
JulianR,

7
@JulianR: Sì, alcuni lo fanno, altri no. Nella nostra configurazione build di rilascio, trattiamo gli avvisi come errori. Quindi non funziona.
Stefan Steinegger,

8
Grazie per la nota; Prenderò questo nel database dei bug e vedremo se possiamo risolverlo per C # 4.0. (Nessuna promessa - dato che si tratta di un caso d'angolo non realistico e solo di un avvertimento, potremmo puntare a risolverlo.)
Eric Lippert,

3
@Stefan: Poiché si tratta di un bug e non di un avviso "vero", è possibile utilizzare un'istruzione #pragma per sopprimere l'avviso per ottenere il passaggio del codice nella build di rilascio.
Martin RL,

25

Come hai già scoperto, poiché i metodi di estensione sono semplicemente metodi statici glorificati, saranno chiamati con nullriferimenti passati, senza NullReferenceExceptionessere lanciati. Ma dal momento che sembrano chiamanti metodi di istanza per il chiamante, dovrebbero anche comportarsi come tali. Dovresti quindi, il più delle volte, controllare il thisparametro e generare un'eccezione se lo è null. Va bene non farlo se il metodo si occupa esplicitamente dei nullvalori e il suo nome lo indica debitamente, come negli esempi seguenti:

public static class StringNullExtensions { 
  public static bool IsNullOrEmpty(this string s) { 
    return string.IsNullOrEmpty(s); 
  } 
  public static bool IsNullOrBlank(this string s) { 
    return s == null || s.Trim().Length == 0; 
  } 
}

Ho anche scritto un post su questo blog qualche tempo fa.


3
Ho votato a favore perché è corretto e ha senso per me (e ben scritto), mentre preferisco anche l'uso descritto nella risposta di @Marc Gravell.
qxotk,

17

Un valore null verrà passato al metodo di estensione.

Se il metodo tenta di accedere all'oggetto senza verificare che sia nullo, quindi sì, genererà un'eccezione.

Un ragazzo qui ha scritto i metodi di estensione "IsNull" e "IsNotNull" che controllano che il riferimento sia passato o meno. Personalmente penso che questa sia un'aberrazione e non avrei dovuto vedere la luce del giorno, ma è perfettamente valida c #.


18
In effetti, per me è come chiedere a un cadavere "Sei vivo" e ottenere una risposta di "no". Un cadavere non può rispondere a nessuna domanda, né dovresti essere in grado di "chiamare" un metodo su un oggetto null.
Binary Worrier,

14
Non sono d'accordo con la logica di Binary Worrier, poiché è utile poter chiamare le estensioni senza preoccuparsi dei riferimenti null, ma +1 per il valore della commedia per analogia :-)
Tim Abell

10
In realtà, a volte non sai se qualcuno è morto, quindi chiedi ancora, e la persona potrebbe rispondere, "no, solo a riposo con gli occhi chiusi"
Nurchi,

7
Quando è necessario concatenare più operazioni (diciamo 3+), è possibile (supponendo che non vi siano effetti collaterali) trasformare diverse linee di noioso codice di controllo null della piastra di cottura in un one-liner elegantemente incatenato con metodi di estensione "null-safe". (Simile all'operatore ".?" Suggerito, ma certamente non altrettanto elegante.) Se non è ovvio un'estensione è "nulla-sicura" di solito prefisso il metodo con "Sicuro", quindi se per esempio è una copia- metodo, il suo nome potrebbe essere "SafeCopy" e restituirebbe null se l'argomento fosse null.
AnorZaken,

3
Ho riso così tanto con la risposta di @BinaryWorrier ahahah ah mi sono visto prendere a calci un corpo per controllare se fosse morto o no ahahh Quindi nella mia immaginazione, che ho controllato se il corpo era morto o no ero io e non il corpo stesso, l'implementazione a il controllo era dentro di me e lo prendevo a calci per vedere se si muoveva. Quindi un corpo non sa se è morto o no, l'OMS controlla, lo sa, ora potresti argomentare che potresti "collegare" al corpo un modo per dirti se è morto o no e che secondo me è ciò che un'estensione è per.
Zorkind,

7

Come altri hanno sottolineato, la chiamata di un metodo di estensione su riferimento null fa sì che questo argomento sia nullo e non accadrà nulla di speciale. Questo dà l'idea di usare metodi di estensione per scrivere clausole di guardia.

Puoi leggere questo articolo per esempi: Come ridurre la complessità ciclomatica: clausola di protezione La versione breve è questa:

public static class StringExtensions
{
    public static void AssertNonEmpty(this string value, string paramName)
    {
        if (string.IsNullOrEmpty(value))
            throw new ArgumentException("Value must be a non-empty string.", paramName);
    }
}

Questo è il metodo di estensione della classe stringa che può essere chiamato su riferimento null:

((string)null).AssertNonEmpty("null");

La chiamata funziona correttamente solo perché il runtime chiamerà correttamente il metodo di estensione su riferimento null. Quindi è possibile utilizzare questo metodo di estensione per implementare clausole di protezione senza sintassi disordinata:

    public IRegisteredUser RegisterUser(string userName, string referrerName)
    {

        userName.AssertNonEmpty("userName");
        referrerName.AssertNonEmpty("referrerName");

        ...

    }

3

Il metodo di estensione è statico, quindi se non fai nulla per questo MyObject non dovrebbe essere un problema, un test rapido dovrebbe verificarlo :)


-1

Ci sono alcune regole d'oro quando vuoi che sia leggibile e verticale.

  • vale la pena dire che Eiffel dice che il codice specifico incapsulato in un metodo dovrebbe funzionare contro alcuni input, che il codice è praticabile se vengono soddisfatte alcune condizioni preliminari e garantisce un risultato atteso

Nel tuo caso - DesignByContract non funziona ... eseguirai una logica su un'istanza nulla.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.