(this == null) en C #!

129

En raison d'un bogue corrigé en C # 4, le programme suivant s'imprime 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 InvalidProgramException. (En mode débogage, cela fonctionne très bien)

Dans VS2010 Beta 2, il ne compile pas (je n'ai pas essayé la Beta 1); J'ai appris que la manière dure

Existe-t-il un autre moyen de créer this == nullen pur C #?

SLaks
la source
3
C'est probablement un bogue dans le compilateur C # 3.0. Cela fonctionne comme il se doit en C # 4.0.
Mehrdad Afshari
82
@SLaks: Le problème avec les bogues est que vous pouvez vous attendre à ce qu'ils soient corrigés à un moment donné, donc les trouver "utiles" n'est probablement pas sage.
AnthonyWJones
6
Merci! ne savait pas pour LINQPad. c'est cool!
thorn̈
8
En quoi, exactement, est-ce utile?
Allen Rice
6
comment ce bogue a-t-il été utile?
BlackTigerX

Réponses:

73

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

La bonne réponse de Marc à cette question indique que selon la spécification (section 7.5.7), vous ne devriez pas pouvoir accéder thisdans 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, il s'agit d'une erreur de compilation):

§ 7.5.7 Cet accès

Un this-access se compose du mot réservé this.

cet accès:

this

Un this-access n'est autorisé que dans le bloc d'un constructeur d'instance, d'une méthode d'instance ou d'un accesseur d'instance.

Mehrdad Afshari
la source
2
Je ne vois pas pourquoi dans le code présenté dans cette question, l'utilisation du mot-clé "ceci" est invalide. La méthode CheckNull est une méthode d'instance normale, non statique . L'utilisation de "this" est valide à 100% dans une telle méthode, et même comparer ceci à null est valide. L'erreur se trouve dans la ligne d'initialisation de base: il s'agit de la tentative de transmission d'un délégué lié à une instance en tant que paramètre au ctor de base. C'est le bogue (un trou dans les vérifications sématiques) dans le compilateur: cela ne devrait PAS être possible. Vous n'êtes pas autorisé à écrire : base(CheckNull())si CheckNull n'est pas statique, et de même, vous ne devriez pas être en mesure d'insérer un lambda lié à une instance.
quetzalcoatl
4
@quetzalcoatl: thisdans la CheckNullméthode est légal. Ce qui n'est pas légal, c'est l' accès implicite this- in () => CheckNull(), essentiellement () => this.CheckNull(), qui s'exécute en dehors du bloc d'un constructeur d'instance. Je conviens que la partie de la spécification que je cite est principalement axée sur la légalité syntaxique du thismot-clé, et probablement une autre partie aborde cette question plus précisément, mais il est également facile d'extrapoler conceptuellement à partir de cette partie de la spécification.
Mehrdad Afshari
2
Désolé, je ne suis pas d'accord. Bien que je sache cela (et que je l'ai écrit dans le commentaire ci-dessus) et que vous le savez également, vous n'avez pas mentionné la cause réelle du problème dans votre réponse (acceptée). La réponse est acceptée - donc, apparemment, l'auteur l'a également saisie. Mais je doute que tous les lecteurs soient aussi brillants et maîtrisent les lambdas pour reconnaître à première vue un lambda lié à une instance par rapport à un lambda statique et le mapper à `` ceci '' et aux problèmes liés à l'IL émis :) C'est pourquoi j'ai ajouté mes trois cents. En dehors de cela, je suis d'accord avec tout ce qui a été trouvé, analysé et décrit par vous et d'autres :)
quetzalcoatl
24

La décompilation brute (réflecteur sans optimisations) du binaire du 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), il 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, donc elle essaie 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 plante en le transformant en C #)


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

SLaks
la source
11

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

texte alternatif

leppie
la source
2
Était en retard, c'était un signe que je devrais arrêter de coder :) Nous avons piraté notre avec DLR stuff IIRC.
leppie
faire un visualiseur de débogueur (DebuggerDisplay) pour tout ce que «c'est», et vous faire tromper ce qui est nul? : D just sayin '
10

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

Je pourrais créer un "bogue" 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 bogue quand vous êtes mordu.


la source
14
C'est un bogue du compilateur. Il génère une IL invalide. (Lire ma réponse)
SLaks
Le contexte est statique, vous ne devriez donc pas être autorisé à une référence de méthode d'instance à ce stade.
leppie
10
@Will: C'est un bogue du compilateur. Le compilateur est censé générer un code valide et vérifiable pour cet extrait de code ou cracher un message d'erreur. Lorsqu'un compilateur ne se comporte pas conformément à la spécification, il est bogué .
Mehrdad Afshari
2
@ Will # 4: Quand j'ai écrit le code, je n'avais pas pensé aux implications. J'ai seulement réalisé que cela n'avait pas de sens quand il a arrêté de compiler dans VS2010. -
SLaks
3
À propos, l'appel de méthode virtuelle dans le constructeur est une opération complètement valide. Ce n'est tout simplement pas recommandé. Cela peut entraîner des catastrophes logiques, mais jamais un InvalidProgramException.
Mehrdad Afshari
3

Je peux me tromper, mais je suis presque sûr que si votre objectif est nullqu'il n'y aura jamais de scénario où cela thiss'applique.

Par exemple, comment appelleriez-vous CheckNull?

Derived derived = null;
Console.WriteLine(derived.CheckNull()); // this should throw a NullReferenceException
Dan Tao
la source
3
Dans un lambda dans l'argument constructeur. Lisez l'intégralité de l'extrait de code. (Et essayez-le si vous ne me croyez pas)
SLaks
Je suis d'accord bien que je me souvienne légèrement de la façon dont en C ++ un objet n'avait pas de référence dans son constructeur et je me demande si le scénario (this == null) est utilisé dans ces cas pour vérifier si un appel à une méthode était fait à partir du constructeur de l'objet avant d'exposer un pointeur vers "this". Cependant, pour autant que je sache en C #, il ne devrait y avoir aucun cas où "this" serait jamais nul, même pas dans les méthodes Dispose ou de finalisation.
jpierson
Je suppose que mon argument est que l'idée même de thiss'exclut mutuellement de la possibilité d'être nul - une sorte de "Cogito, ergo sum" de la programmation informatique. Par conséquent, votre désir d'utiliser l'expression this == nullet de la faire revenir vrai me semble erroné.
Dan Tao
En d'autres termes: j'ai lu votre code; ce que je dis, c'est que je remets en question ce que vous essayiez d'accomplir en premier lieu.
Dan Tao
Ce code illustre simplement le bogue et, comme vous l'avez souligné, est totalement inutile. Pour voir le vrai code utile, lisez ma deuxième réponse.
SLaks
-1

Je ne sais pas si c'est ce que vous recherchez

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

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

Scott et l'équipe de développement
la source
13
Vous avez complètement mal compris la question.
SLaks
1
Je ai pensé autant. Je pensais que j'essaierais de toute façon.
Scott et l'équipe de développement