71 votes

Pourquoi cochez-le ! = null ?

Parfois, j'aime passer du temps sur le .NET code pour voir comment les choses sont mises en œuvre en coulisses. Je suis tombé sur ce petit bijou tout en regardant à l' String.Equals méthode via le Réflecteur.

C#

[ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)]
public override bool Equals(object obj)
{
    string strB = obj as string;
    if ((strB == null) && (this != null))
    {
        return false;
    }
    return EqualsHelper(this, strB);
}

IL

.method public hidebysig virtual instance bool Equals(object obj) cil managed
{
    .custom instance void System.Runtime.ConstrainedExecution.ReliabilityContractAttribute::.ctor(valuetype System.Runtime.ConstrainedExecution.Consistency, valuetype System.Runtime.ConstrainedExecution.Cer) = { int32(3) int32(1) }
    .maxstack 2
    .locals init (
        [0] string str)
    L_0000: ldarg.1 
    L_0001: isinst string
    L_0006: stloc.0 
    L_0007: ldloc.0 
    L_0008: brtrue.s L_000f
    L_000a: ldarg.0 
    L_000b: brfalse.s L_000f
    L_000d: ldc.i4.0 
    L_000e: ret 
    L_000f: ldarg.0 
    L_0010: ldloc.0 
    L_0011: call bool System.String::EqualsHelper(string, string)
    L_0016: ret 
}

Quel est le raisonnement pour le contrôle this contre null? Je dois supposer qu'il y a but, sinon cela aurait probablement été capturé et enlevé par maintenant.

85voto

Jon Skeet Points 692016

Je suppose que vous cherchez à la .NET 3.5 mise en œuvre? Je crois que l' .NET 4 mise en œuvre est légèrement différente.

Cependant, j'ai l'intuition que c'est parce qu'il est possible de faire appel, même virtuel méthodes d'instance non-pratiquement sur une référence null. Possible dans l'ILLINOIS, qui est. Je vais voir si je peux produire de l'IL qui appellent null.Equals(null).

EDIT: Bon, voici quelques codes intéressants:

.method private hidebysig static void  Main() cil managed
{
  .entrypoint
  // Code size       17 (0x11)
  .maxstack  2
  .locals init (string V_0)
  IL_0000:  nop
  IL_0001:  ldnull
  IL_0002:  stloc.0
  IL_0003:  ldloc.0
  IL_0004:  ldnull
  IL_0005:  call instance bool [mscorlib]System.String::Equals(string)
  IL_000a:  call void [mscorlib]System.Console::WriteLine(bool)
  IL_000f:  nop
  IL_0010:  ret
} // end of method Test::Main

Je l'ai obtenu par la compilation du code C# suivant:

using System;

class Test
{
    static void Main()
    {
        string x = null;
        Console.WriteLine(x.Equals(null));

    }
}

... et puis à démonter avec ildasm et l'édition. Remarque cette ligne:

IL_0005:  call instance bool [mscorlib]System.String::Equals(string)

À l'origine, qui a été callvirt au lieu de call.

Donc, ce qui arrive quand nous le remonter? Eh bien, avec .NET 4.0 nous obtenons ceci:

Unhandled Exception: System.NullReferenceException: Object
reference not set to an instance of an object.
    at Test.Main()

Hmm. Ce sur avec .NET 2.0?

Unhandled Exception: System.NullReferenceException: Object reference 
not set to an instance of an object.
   at System.String.EqualsHelper(String strA, String strB)
   at Test.Main()

Maintenant, c'est plus intéressant... nous avons clairement réussi à obtenir en EqualsHelper, ce qui nous n'aurions pas normalement.

Assez de ficelle... nous allons essayer de mettre en œuvre la référence à l'égalité de nous-mêmes, et de voir si nous pouvons obtenir de l' null.Equals(null) retourner la valeur true:

using System;

class Test
{
    static void Main()
    {
        Test x = null;
        Console.WriteLine(x.Equals(null));
    }

    public override int GetHashCode()
    {
        return base.GetHashCode();
    }

    public override bool Equals(object other)
    {
        return other == this;
    }
}

Même procédure qu'avant de démonter, modifier callvirt de call, remonter, et de le regarder à imprimer true...

Notez que bien que l'autre répond références de ce C++ question, nous sommes d'être encore plus sournois ici... parce que nous allons appeler un virtuel méthode non-virtuelle. Normalement, même le C++/CLI compilateur utilisera callvirt pour une méthode virtuelle. En d'autres termes, je pense que dans ce cas particulier, la seule façon pour this à la valeur null est d'écrire le IL par la main.


EDIT: je viens de remarquer quelque chose... je n'ai pas fait appel de la méthode en soit de notre petit échantillon de programmes. Voici l'appel dans le premier cas:

IL_0005:  call instance bool [mscorlib]System.String::Equals(string)

voici l'appel de la deuxième:

IL_0005:  call instance bool [mscorlib]System.Object::Equals(object)

Dans le premier cas, j'ai signifié à l'appel System.String::Equals(object), et dans le deuxième, j'ai signifié à l'appel Test::Equals(object). De cela, nous pouvons voir trois choses:

  • Vous devez être prudent avec la surcharge.
  • Le compilateur C# émet des appels pour le preneur de la méthode virtuelle - pas le plus spécifique de la remplacer de la méthode virtuelle. IIRC, VB fonctionne à l'inverse
  • object.Equals(object) est heureux de comparer un nul "ce" de référence

Si vous ajoutez un peu de console de sortie pour le C# remplacer, vous pouvez voir la différence, il ne sera pas appelé, sauf si vous modifiez le IL à l'appeler explicitement, comme ceci:

IL_0005:  call   instance bool Test::Equals(object)

Donc, nous y sommes. Le plaisir et l'abus de méthodes d'instance sur des références nulles.

Si vous avez rendu à ce point, vous pouvez aussi regarder mon blog à propos de la façon dont les types de valeur peut déclarer sans paramètre constructeurs... dans IL.

17voto

JaredPar Points 333733

La raison en est qu'il est en effet possible pour this être null. Il y a 2 IL op codes qui peuvent être utilisés pour appeler une fonction: call et callvirt. Le callvirt fonction provoque le CLR pour effectuer un null vérifier lors de l'invocation de la méthode. L'appel de l'instruction n'a pas et permet donc une méthode pour être entré this être null.

Son effrayant? En effet, il est un peu. Cependant, la plupart des compilateurs assurer cela ne arrivera jamais. L' .appel d'instruction n'est jamais sorti lors de l' null n'est pas une possibilité (je suis sûr que C# utilise toujours callvirt).

Ce n'est pas vrai pour toutes les langues, bien que pour des raisons que je ne sais pas exactement la BCL équipe a choisi de durcir le System.String de la classe dans cette instance.

Un autre cas où cela peut pop-up est dans le sens inverse pinvoke appels.

9voto

Warren Points 2273

La réponse courte est que les langages tels que C# vous forcer à créer une instance de cette classe avant d'appeler la méthode, mais le Cadre lui-même ne l'est pas. Il y a two1 différentes façons de CIL pour appeler une fonction: call et callvirt.... Généralement parlant, C# sera toujours émettent callvirt, ce qui exige this à ne pas être null. Mais d'autres langages (C++/CLI vient à l'esprit) peut émettre call, ce qui n'a pas cette attente.

(1okay, c'est plus de cinq si l'on compte calli, newobj etc, mais faisons simple)

4voto

Eugene Beresovksy Points 3852

Le code source a ce commentaire :

Cela est nécessaire pour se prémunir contre les revers-pinvokes et aux autres appelants qui n’utilisent pas l’instruction callvirt

1voto

Steve Wortham Points 11563

Voyons voir... this est la première chaîne que vous comparez. obj est le deuxième objet. Donc, on dirait que c'est une optimisation de toutes sortes. C'est le premier casting obj pour un type de chaîne. Et si cela échoue, alors strB a la valeur null. Et si strB est null alors qu' this n'est pas le cas, alors ils ne sont certainement pas de l'égalité et de l' EqualsHelper fonction peut être ignorée.

Qui va sauver un appel de fonction. Au-delà, peut-être une meilleure compréhension de l' EqualsHelper fonction pourrait faire la lumière sur les raisons de cette optimisation est nécessaire.

EDIT:

Ah, donc le EqualsHelper fonction est d'accepter un (string, string) en tant que paramètres. Si strB est nulle, alors cela signifie essentiellement que c'est soit un objet nul pour commencer, ou qu'il ne pouvait pas réussi à être jeté dans une chaîne de caractères. Si la raison de l' strB nul, c'est que l'objet était un autre type qui n'a pas pu être converti en chaîne de caractères, alors vous ne souhaitez pas appeler EqualsHelper avec essentiellement deux valeurs null (qui va retourner la valeur true). L'est Égal à la fonction doit retourner false dans le cas présent. Donc, si la déclaration est plus qu'une optimisation, il assure effectivement une bonne fonctionnalité.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X