web-dev-qa-db-fra.com

(this == null) en C #!

En raison d'un bogue corrigé en C # 4, le programme suivant affiche true. (Essayez-le dans 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()) { }
}

Dans VS2008 en mode Release, il lève une exception InvalidProgramException. (En mode débogage, cela fonctionne bien)

Dans VS2010 Beta 2, il ne se compile pas (je n'ai pas essayé Beta 1); J'ai appris à la dure

Existe-t-il un autre moyen de faire this == null en C # pur?

129
SLaks

Cette observation a été publiée sur StackOverflow dans ne autre question plus tôt dans la journée.

Marc 's excellente réponse à cette question indique que selon les spécifications (section 7.5.7), vous ne devriez pas pouvoir accéder à this dans ce contexte et la possibilité de le faire dans le compilateur C # 3.0 est un bogue. Le compilateur C # 4.0 se comporte correctement selon la spécification (même en Bêta 1, c'est une erreur de temps de compilation):

§ 7.5.7 Cet accès

A cet accès se compose du mot réservé this.

cet accès:

this

Un cet accès n'est autorisé que dans le bloc d'un constructeur d'instance, d'une méthode d'instance ou d'un accesseur d'instance.

73
Mehrdad Afshari

La décompilation brute (Reflector sans optimisation) du binaire en mode Debug est:

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

La méthode CompilerGenerated n'a pas de sens; si vous regardez l'IL (ci-dessous), cela appelle la méthode sur une chaîne nulle (!).

   .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 

En mode Release, la variable locale est optimisée, elle essaie donc de pousser une variable inexistante sur la pile.

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

(Le réflecteur se bloque lors de sa transformation en C #)


EDIT : Est-ce que quelqu'un (Eric Lippert?) sait pourquoi le compilateur émet le ldloc?

24
SLaks

J'ai eu ça! (et j'ai aussi une preuve)

alt text

11
leppie

Ce n'est pas un "bug". C'est vous qui abusez du système de type. Vous n'êtes jamais censé transmettre une référence à l'instance actuelle (this) à quiconque au sein d'un constructeur.

Je pourrais créer un "bug" similaire en appelant également une méthode virtuelle dans le constructeur de la classe de base.

Ce n'est pas parce que vous pouvez faire quelque chose de mal que c'est un bug lorsque vous vous en sortez.

10
Will

Je peux me tromper, mais je suis sûr que si votre objet est null, il n'y aura jamais de scénario où this s'applique.

Par exemple, comment appelleriez-vous CheckNull?

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