(this == null) in C #!


129

A causa di un errore corretto in C # 4, viene stampato il seguente programma true. (Provalo in LINQPad)

void Main() { new Derived(); }

class Base {
    public Base(Func<string> valueMaker) { Console.WriteLine(valueMaker()); }
}
class Derived : Base {
    string CheckNull() { return "Am I null? " + (this == null); }
    public Derived() : base(() => CheckNull()) { }
}

In VS2008 in modalità Release, genera un InvalidProgramException. (In modalità Debug, funziona benissimo)

In VS2010 Beta 2, non viene compilato (non ho provato Beta 1); L'ho imparato nel modo più duro

C'è un altro modo di fare this == nullin C # puro?


3
È molto probabilmente un bug nel compilatore C # 3.0. Funziona come dovrebbe in C # 4.0.
Mehrdad Afshari,

82
@SLaks: Il problema con i bug è che puoi aspettarti che vengano risolti ad un certo punto, quindi trovarli "utili" probabilmente non è saggio.
AnthonyWJones,

6
Grazie! non sapevo di LINQPad. è bello!
thorn̈

8
In che modo, esattamente, è utile?
Allen Rice,

6
come è stato utile questo errore?
BlackTigerX,

Risposte:


73

Questa osservazione è stata pubblicata su StackOverflow in un'altra domanda all'inizio di oggi.

La grande risposta di Marc a questa domanda indica che, secondo le specifiche (sezione 7.5.7), non dovresti essere in grado di accedere thisin quel contesto e la possibilità di farlo nel compilatore C # 3.0 è un bug. Il compilatore C # 4.0 si comporta correttamente in base alle specifiche (anche in Beta 1, si tratta di un errore di tempo di compilazione):

§ 7.5.7 Questo accesso

Un accesso di questo consiste nella parola riservata this.

questo accesso:

this

Un accesso di questo è consentito solo nel blocco di un costruttore di istanze, di un metodo di istanza o di un accessor di istanza.


2
Non vedo, perché nel codice presentato in questa domanda, l'uso della parola chiave "this" non è valido. Il metodo CheckNull è un metodo di istanza normale, non statico . L'uso di "this" è valido al 100% in tale metodo e anche il confronto con null è valido. L'errore si trova nella riga iniziale di base: è il tentativo di passare il delegato limitato all'istanza come parametro al ctor di base. Questo è il bug (un buco nei controlli sematici) nel compilatore: NON dovrebbe essere possibile. Non è consentito scrivere : base(CheckNull())se CheckNull non è statico e allo stesso modo non si dovrebbe essere in grado di incorporare un lambda associato all'istanza.
quetzalcoatl,

4
@quetzalcoatl: thisnel CheckNullmetodo è legale. Ciò che non è legale è implicita questo accesso a () => CheckNull(), essenzialmente () => this.CheckNull(), che è in esecuzione di fuori del blocco di un costruttore di istanza. Concordo sul fatto che la parte di specifica che cito è principalmente focalizzata sulla legalità sintattica della thisparola chiave, e probabilmente un'altra parte affronta questo problema in modo più preciso, ma è facile estrapolare concettualmente anche da questa parte di specifica.
Mehrdad Afshari,

2
Scusa, non sono d'accordo. Mentre lo so (e l'ho scritto nel commento sopra) e lo sai anche tu - non hai menzionato la vera causa del problema nella tua (accettata) risposta. La risposta è stata accettata, quindi a quanto pare anche l'autore l'ha presa. Ma dubito che tutti i lettori saranno altrettanto brillanti e fluenti in lambda a riconoscere a prima vista un'istanza-lambda contro statica-lambda e mapparla a "questo" e problemi con IL emesso :) Ecco perché ho aggiunto i miei tre centesimi. A parte questo, sono d'accordo con tutto ciò che è stato trovato, analizzato e descritto da te e dagli altri :)
quetzalcoatl

24

La decompilazione non elaborata (Reflector senza ottimizzazioni) del binario della modalità Debug è:

private class Derived : Program.Base
{
    // Methods
    public Derived()
    {
        base..ctor(new Func<string>(Program.Derived.<.ctor>b__0));
        return;
    }

    [CompilerGenerated]
    private static string <.ctor>b__0()
    {
        string CS$1$0000;
        CS$1$0000 = CS$1$0000.CheckNull();
    Label_0009:
        return CS$1$0000;
    }

    private string CheckNull()
    {
        string CS$1$0000;
        CS$1$0000 = "Am I null? " + ((bool) (this == null));
    Label_0017:
        return CS$1$0000;
    }
}

Il metodo CompilerGenerated non ha senso; se guardi l'IL (sotto), sta chiamando il metodo su una stringa nulla (!).

   .locals init (
        [0] string CS$1$0000)
    L_0000: ldloc.0 
    L_0001: call instance string CompilerBug.Program/Derived::CheckNull()
    L_0006: stloc.0 
    L_0007: br.s L_0009
    L_0009: ldloc.0 
    L_000a: ret 

In modalità Release, la variabile locale è ottimizzata, quindi tenta di inserire una variabile inesistente nello stack.

    L_0000: ldloc.0 
    L_0001: call instance string CompilerBug.Program/Derived::CheckNull()
    L_0006: ret 

(Il riflettore si arresta in modo anomalo quando lo si trasforma in C #)


EDIT : Qualcuno (Eric Lippert?) Sa perché il compilatore emette il ldloc?


11

L'ho avuto! (e anche le prove)

testo alternativo


2
Era in ritardo, era un segno che avrei dovuto smettere di scrivere codice :) Stava hackerando il nostro con roba DLR IIRC.
leppie,

creare un visualizzatore di debugger (DebuggerDisplay) per qualunque cosa sia "questo" e renderti scemo che sia nullo? : D, basta

10

Questo non è un "bug". Stai abusando del sistema dei tipi. Non devi mai passare un riferimento all'istanza corrente ( this) a nessuno all'interno di un costruttore.

Potrei creare un "bug" simile chiamando anche un metodo virtuale all'interno del costruttore della classe base.

Solo perché puoi fare qualcosa di brutto non significa che sia un bug quando ci si sente un po '.


14
È un bug del compilatore. Genera IL non valido. (Leggi la mia risposta)
SLaks

Il contesto è statico, quindi non dovrebbe essere consentito un riferimento al metodo di istanza in quella fase.
leppie,

10
@Will: è un bug del compilatore. Il compilatore dovrebbe generare un codice valido e verificabile per quel frammento di codice o emettere un messaggio di errore. Quando un compilatore non si comporta in base alle specifiche, è difettoso .
Mehrdad Afshari,

2
@ Will # 4: quando ho scritto il codice, non avevo pensato alle implicazioni. Mi sono reso conto che non aveva senso quando ha smesso di compilare in VS2010. -
SLaks,

3
A proposito, la chiamata al metodo virtuale nel costruttore è un'operazione completamente valida. Non è solo raccomandato. Si può portare a disastri logici, ma mai una InvalidProgramException.
Mehrdad Afshari,

3

Potrei sbagliarmi, ma sono abbastanza sicuro se il tuo oggetto è che nullnon ci sarà mai uno scenario in cui si thisapplichi.

Ad esempio, come chiameresti CheckNull?

Derived derived = null;
Console.WriteLine(derived.CheckNull()); // this should throw a NullReferenceException

3
In una lambda nell'argomento del costruttore. Leggi l'intero snippet di codice. (E provalo se non mi credi)
SLaks,

Sono d'accordo anche se ricordo leggermente qualcosa su come in C ++ un oggetto non avesse riferimenti all'interno del suo costruttore e mi chiedo se lo scenario (this == null) sia usato in quei casi per verificare se una chiamata a un metodo era realizzato dal costruttore dell'oggetto prima di esporre un puntatore a "this". Tuttavia, per quanto ne so in C #, non dovrebbero esserci casi in cui "questo" sarebbe mai nullo, nemmeno nei metodi di eliminazione o finalizzazione.
jpierson,

Immagino che il mio punto sia che l'idea stessa di thissi escluda a vicenda dalla possibilità di essere null - una specie di "Cogito, ergo sum" di programmazione per computer. Quindi il tuo desiderio di usare l'espressione this == nulle averlo sempre restituito vero mi colpisce come fuorviato.
Dan Tao,

In altre parole: ho letto il tuo codice; quello che sto dicendo è che metto in dubbio ciò che stavi cercando di realizzare in primo luogo.
Dan Tao,

Questo codice dimostra semplicemente il bug e, come hai sottolineato, è assolutamente inutile. Per vedere il vero codice utile, leggi la mia seconda risposta.
SLaks,

-1

Non sono sicuro se questo è quello che stai cercando

    public static T CheckForNull<T>(object primary, T Default)
    {
        try
        {
            if (primary != null && !(primary is DBNull))
                return (T)Convert.ChangeType(primary, typeof(T));
            else if (Default.GetType() == typeof(T))
                return Default;
        }
        catch (Exception e)
        {
            throw new Exception("C:CFN.1 - " + e.Message + "Unexpected object type of " + primary.GetType().ToString() + " instead of " + typeof(T).ToString());
        }
        return default(T);
    }

esempio: UserID = CheckForNull (Request.QueryString ["UserID"], 147);


13
Hai frainteso completamente la domanda.
SLaks

1
C'ero arrivato pure io. Ho pensato di provare comunque.
Scott e il Dev Team
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.