Pourquoi est-il valide de concaténer des chaînes nulles mais pas d'appeler «null.ToString ()»?

126

C'est un code C # valide

var bob = "abc" + null + null + null + "123";  // abc123

Ce n'est pas un code C # valide

var wtf = null.ToString(); // compiler error

Pourquoi la première déclaration est-elle valide?

superlogique
la source
97
Je trouve étrange qu'on vous null.ToString()donne ce nom wtf. Pourquoi cela vous surprend-il? Vous ne pouvez pas appeler une méthode d'instance lorsque vous n'avez rien à partir duquel l'appeler en premier lieu.
BoltClock
11
@BoltClock: Bien sûr, il est possible d'appeler une méthode d'instance sur une instance nulle. Juste pas possible en C #, mais très valable sur le CLR :)
leppie
1
Les réponses concernant String.Concat sont presque correctes. En fait, l'exemple spécifique de la question est celui d' un repliement constant , et les valeurs nulles de la première ligne sont éliminées par le compilateur - c'est-à-dire qu'elles ne sont jamais évaluées à l'exécution car elles n'existent plus - le compilateur les a effacées. J'ai écrit un petit monologue sur toutes les règles de concaténation et de pliage constant des chaînes ici pour plus d'informations: stackoverflow.com/questions/9132338/… .
Chris Shain
77
vous ne pouvez rien ajouter à une chaîne, mais vous ne pouvez pas créer une chaîne à partir de rien.
RVB
La deuxième déclaration n'est-elle pas valide? class null_extension { String ToString( Object this arg ) { return ToString(arg); } }
ctrl-alt-delor

Réponses:

163

La raison pour laquelle le premier fonctionne:

Depuis MSDN :

Dans les opérations de concaténation de chaînes, le compilateur C # traite une chaîne nulle de la même manière qu'une chaîne vide, mais il ne convertit pas la valeur de la chaîne nulle d'origine.

Plus d'informations sur l' opérateur binaire + :

L'opérateur binaire + effectue la concaténation de chaînes lorsqu'un ou les deux opérandes sont de type chaîne.

Si un opérande de concaténation de chaînes est nul, une chaîne vide est remplacée. Sinon, tout argument non-chaîne est converti en sa représentation sous forme de chaîne en appelant la ToStringméthode virtuelle héritée de type object.

Si ToStringretourne null, une chaîne vide est remplacée.

La raison de l'erreur en seconde est:

null (référence C #) - Le mot clé null est un littéral qui représente une référence null, une référence qui ne fait référence à aucun objet. null est la valeur par défaut des variables de type référence.

Pranay Rana
la source
1
Cela fonctionnerait-il alors? var wtf = ((String)null).ToString();Je travaille récemment en Java où le cast de null est possible, cela fait un moment que j'ai travaillé avec C #.
Jochem
14
@Jochem: Vous essayez toujours d'appeler une méthode sur un objet nul, donc je suppose que non. Pensez-y comme null.ToString()vs ToString(null).
Svish
@Svish oui, maintenant j'y repense, c'est un objet nul, donc tu as raison, ça ne marchera pas. Ce ne serait pas non plus en Java: exception de pointeur nul. Ça ne fait rien. Tnx pour votre réponse! [modifier: testé en Java: NullPointerException. Avec la différence qu'avec le cast, il compile, sans le cast, il ne le fait pas].
Jochem
il n'y a généralement aucune raison de concaténer ou de ToString intentionnellement le mot clé null. Si vous avez besoin d'une chaîne vide, utilisez string.Empty. si vous avez besoin de vérifier si une variable chaîne est nulle, vous pouvez utiliser (myString == null) ou string.IsNullOrEmpty (myString). Vous pouvez également transformer une variable de chaîne nulle en chaîne.Empty utiliser myNewString = (myString == null ?? string.Empty)
csauve
@Jochem le castant le fera compiler mais il lancera effectivement une NullReferenceException au moment de l'exécution
jeroenh
108

Parce que l' +opérateur en C # se traduit en interne par String.Concat, qui est une méthode statique. Et cette méthode se traite nullcomme une chaîne vide. Si vous regardez la source de String.ConcatReflector, vous la verrez:

// while looping through the parameters
strArray[i] = (str == null) ? Empty : str;
// then concatenate that string array

(MSDN le mentionne également: http://msdn.microsoft.com/en-us/library/k9c94ey1.aspx )

D'autre part, ToString()est une méthode d'instance, que vous ne pouvez pas appeler null(pour quel type utiliser null?).

Botz3000
la source
2
Il n'y a cependant pas d'opérateur + dans la classe String. Alors, est-ce le compilateur qui traduit en String.Concat?
superlogical
@superlogical Oui, c'est ce que fait le compilateur. Donc en fait, l' +opérateur sur les chaînes en C # n'est que du sucre syntaxique pour String.Concat.
Botz3000
Ajout à cela: le compilateur sélectionne automatiquement la surcharge de Concat qui a le plus de sens. Les surcharges disponibles sont 1, 2 ou 3 paramètres d'objet, 4 paramètres d'objet + __arglistet une paramsversion de tableau d'objets.
Wormbo
Quel tableau de chaînes y est modifié? Crée-t-il Concattoujours un nouveau tableau de chaînes non nulles même lorsqu'on lui donne un tableau de chaînes non nulles, ou est-ce qu'il se passe autre chose?
supercat
@supercat Le tableau modifié est un nouveau tableau, qui est ensuite passé à une méthode d'assistance privée. Vous pouvez consulter le code vous-même à l'aide du réflecteur.
Botz3000
29

Le premier échantillon sera traduit en:

var bob = String.Concat("abc123", null, null, null, "abs123");

La Concatméthode vérifie l'entrée et traduit null comme une chaîne vide

Le deuxième échantillon sera traduit en:

var wtf = ((object)null).ToString();

Une nullexception de référence sera donc générée ici

Viacheslav Smityukh
la source
En fait, un AccessViolationExceptionsera jeté :)
leppie
Je viens de le faire :) ((object)null).ToString()=> AccessViolation ((object)1 as string).ToString()=>NullReference
leppie
1
J'ai NullReferenceException. DN 4.0
Viacheslav Smityukh
Intéressant. Quand je mets à l' ((object)null).ToString()intérieur, try/catchje reçois NullReferenceExceptionaussi.
leppie
3
@Ieppie sauf que cela ne lance pas non plus AccessViolation (pourquoi le ferait-il?) - NullReferenceException ici.
jeroenh
11

La première partie de votre code est simplement traitée comme ça dans String.Concat,

qui est ce que le compilateur C # appelle lorsque vous ajoutez des chaînes. " abc" + nullest traduit en String.Concat("abc", null),

et en interne, cette méthode remplace nullpar String.Empty. C'est pourquoi votre première partie de code ne lève aucune exception. c'est juste comme

var bob = "abc" + string.Empty + string.Empty + string.Empty + "123";  //abc123

Et dans la 2ème partie de votre code lève une exception car «null» n'est pas un objet, le mot-clé null est un littéral qui représente une référence nulle , qui ne fait référence à aucun objet. null est la valeur par défaut des variables de type référence.

Et ' ToString()' est une méthode qui peut être appelée par une instance d'un objet mais pas par n'importe quel littéral.

Talha
la source
3
String.Emptyn'est pas égal à null. C'est juste traité de la même manière dans certaines méthodes comme String.Concat. Par exemple, si vous avez une variable de chaîne définie sur null, C # ne la remplacera pas par String.Emptysi vous essayez d'appeler une méthode dessus.
Botz3000
3
Presque. nulln'est pas traité comme String.Emptypar C #, il est simplement traité comme ça dans String.Concat, ce que le compilateur C # appelle lorsque vous ajoutez des chaînes. "abc" + nullest traduit en String.Concat("abc", null), et en interne, cette méthode remplace nullpar String.Empty. La deuxième partie est tout à fait correcte.
Botz3000
9

Dans le framework COM qui a précédé .net, il était nécessaire que toute routine recevant une chaîne la libère lorsqu'elle en avait fini avec elle. Étant donné qu'il était très courant que des chaînes vides soient passées dans et hors des routines et que la tentative de «libération» d'un pointeur nul était définie comme une opération légitime de ne rien faire, Microsoft a décidé de faire en sorte qu'un pointeur de chaîne nulle représente une chaîne vide.

Pour permettre une certaine compatibilité avec COM, de nombreuses routines dans .net interpréteront un objet nul comme une représentation légale comme une chaîne vide. Avec quelques légères modifications .net et ses langages (permettant notamment aux membres d'instance d'indiquer «ne pas invoquer comme virtuel»), Microsoft aurait pu faire en sorte que les nullobjets du type déclaré String se comportent encore plus comme des chaînes vides. Si Microsoft avait fait cela, il aurait également dû faire Nullable<T>fonctionner un peu différemment (afin de permettre - Nullable<String>quelque chose qu'ils auraient dû faire de toute façon à mon humble avis) et / ou définir un NullableStringtype qui serait principalement interchangeable avec String, mais qui ne considérerait pas un nullcomme une chaîne vide valide.

En l'état, il existe certains contextes dans lesquels a nullsera considéré comme une chaîne vide légitime et d'autres dans lesquels il ne le sera pas. Ce n'est pas une situation très utile, mais une situation dont les programmeurs devraient être conscients. En général, les expressions du formulaire stringValue.someMemberéchoueront si stringValuec'est le cas null, mais la plupart des méthodes et opérateurs du framework qui acceptent des chaînes comme paramètres seront considérés nullcomme une chaîne vide.

supercat
la source
5

'+'est un opérateur d'infixe. Comme tout opérateur, il appelle vraiment une méthode. Vous pouvez imaginer une version non infixée"wow".Plus(null) == "wow"

Le réalisateur a décidé de quelque chose comme ça ...

class String
{
  ...
  String Plus(ending)
  {
     if(ending == null) return this;
     ...
  }
} 

Alors ... votre exemple devient

var bob = "abc".Plus(null).Plus(null).Plus(null).Plus("123");  // abc123

qui est le même que

var bob = "abc".Plus("123");  // abc123

À aucun moment, null ne devient une chaîne. Ce null.ToString()n'est donc pas différent de cela null.VoteMyAnswer(). ;)

Nigel Thorne
la source
Vraiment, c'est plutôt var bob = Plus(Plus(Plus(Plus("abc",null),null),null),"123");. Les surcharges d'opérateurs sont des méthodes statiques au cœur: msdn.microsoft.com/en-us/library/s53ehcz3(v=VS.71).aspx Si elles ne l'étaient pas, var bob = null + "abc";ou surtout string bob = null + null;ne seraient pas valides.
Ben Mosher
Vous avez raison, je pensais juste que je serais trop déroutant si je l'expliquais de cette façon.
Nigel Thorne
3

Je suppose que c'est un littéral qui ne fait référence à aucun objet. ToString()a besoin d'un object.

Saeed Neamati
la source
3

Quelqu'un a dit dans ce fil de discussion que vous ne pouvez pas créer de chaîne avec rien. (qui est une belle phrase comme je pense). Mais oui - vous pouvez :-), comme le montre l'exemple suivant:

var x = null + (string)null;     
var wtf = x.ToString();

fonctionne bien et ne lève pas du tout d'exception. La seule différence est que vous devez convertir l'une des valeurs nulles en une chaîne - si vous supprimez la conversion (chaîne) , l'exemple se compile toujours, mais lève une exception d'exécution: "L'opérateur '+' est ambigu sur les opérandes de tapez '<null>' et '<null>' ".

NB Dans l'exemple de code ci-dessus, la valeur de x n'est pas nulle comme on pourrait s'y attendre, c'est en fait une chaîne vide après avoir transtypé l'un des opérandes en une chaîne.


Un autre fait intéressant est qu'en C # / .NET, la manière dont nullest traitée n'est pas toujours la même si vous considérez différents types de données. Par exemple:

int? x = 1;  //  string x = "1";
x = x + null + null;
Console.WriteLine((x==null) ? "<null>" : x.ToString());

Regardez la 1ère ligne de l'extrait de code: si xest une variable entière Nullable (c'est-à-dire int?) contenant une valeur 1, alors vous récupérez le résultat <null>. S'il s'agit d'une chaîne (comme indiqué dans le commentaire) avec une valeur "1", vous récupérez "1"plutôt que <null>.

NB Aussi intéressant: si vous utilisez var x = 1;pour la première ligne, vous obtenez une erreur d'exécution. Pourquoi? Parce que l'affectation transformera la variable xen type de données int, qui n'est pas nullable. Le compilateur ne suppose pas int?ici, et échoue donc dans la 2ème ligne où nullest ajouté.

Mat
la source
2

L'ajout nullà une chaîne est simplement ignoré. null(dans votre deuxième exemple) n'est une instance d'aucun objet, donc il n'a même pas de ToString()méthode. C'est juste un littéral.

Thorsten Dittmar
la source
1

Parce qu'il n'y a aucune différence entre string.Emptyet nullquand vous concattez des chaînes. Vous pouvez également transmettre null string.Format. Mais vous essayez d'appeler une méthode on null, ce qui entraînerait toujours un NullReferenceExceptionet donc générerait une erreur de compilation.
Si, pour une raison quelconque, vous voulez vraiment le faire, vous pouvez écrire une méthode d'extension, qui vérifie nullet retourne string.Empty. Mais une extension comme celle-ci ne devrait être utilisée que lorsqu'elle est absolument nécessaire (à mon avis).

Franky
la source
0

En général: il peut ou non valider l'acceptation de null comme paramètre en fonction de la spécification, mais il est toujours invalide d'appeler une méthode sur null.


C'est et un autre sujet pourquoi les opérandes de l'opérateur + peuvent être nuls dans le cas de chaînes. C'est un peu quelque chose de VB (désolé les gars) pour faciliter la vie des programmeurs, ou en supposant que le programmeur ne peut pas gérer les valeurs nulles. Je suis complètement en désaccord avec cette spécification. 'inconnu' + ​​'n'importe quoi' doit être toujours 'inconnu' ...

g.pickardou
la source