Pourquoi la valeur par défaut du type de chaîne est-elle nulle au lieu d'une chaîne vide?

218

C'est assez ennuyeux de tester toutes mes chaînes nullavant de pouvoir appliquer en toute sécurité des méthodes comme ToUpper(), StartWith()etc ...

Si la valeur par défaut de stringétait la chaîne vide, je n'aurais pas à tester et je la sentirais plus cohérente avec les autres types de valeur comme intou doublepar exemple. De plus, cela Nullable<String>aurait du sens.

Alors pourquoi les concepteurs de C # ont-ils choisi d'utiliser nullcomme valeur par défaut des chaînes?

Remarque: Cela se rapporte à cette question , mais est plus axé sur le pourquoi plutôt que sur ce qu'il faut en faire.

Marcel
la source
53
Considérez-vous cela comme un problème pour d' autres types de référence?
Jon Skeet
17
@JonSkeet Non, mais uniquement parce que je pensais initialement, à tort, que les chaînes sont des types de valeur.
Marcel
21
@Marcel: C'est une assez bonne raison de se poser des questions à ce sujet.
TJ Crowder du
7
@ JonSkeet Oui. Oh oui. (Mais vous n'êtes pas étranger à la discussion sur les types de référence non annulables…)
Konrad Rudolph
7
Je pense que vous auriez un bien meilleur moment si vous utilisiez des assertions sur vos chaînes dans des endroits où vous ne vous attendez pas à ce qu'elles soient null(et je vous recommande également de traiter conceptuellement nullet de vider les chaînes comme des choses différentes). Une valeur nulle peut être le résultat d'une erreur quelque part, tandis qu'une chaîne vide doit transmettre une signification différente.
diegoreymendez

Réponses:

312

Pourquoi la valeur par défaut du type de chaîne est-elle nulle au lieu d'une chaîne vide?

Car stringest un type de référence et la valeur par défaut pour tous les types de référence est null.

C'est assez ennuyeux de tester toutes mes chaînes pour null avant de pouvoir appliquer en toute sécurité des méthodes comme ToUpper (), StartWith () etc ...

Cela est cohérent avec le comportement des types de référence. Avant d'appeler leurs membres d'instance, il convient de mettre en place une vérification pour une référence nulle.

Si la valeur par défaut de chaîne était la chaîne vide, je n'aurais pas à tester et je la sentirais plus cohérente avec les autres types de valeurs comme int ou double par exemple.

Affecter la valeur par défaut à un type de référence spécifique autre que nullle rendrait incohérent .

De plus, cela Nullable<String>aurait du sens.

Nullable<T>fonctionne avec les types de valeur. Il convient de noter le fait qu'il Nullablen'a pas été introduit sur la plate-forme .NET d' origine, il y aurait donc eu beaucoup de code cassé s'ils avaient modifié cette règle. ( Courtesy @jcolebrand )

Habib
la source
10
@HenkHolterman On pourrait implémenter une tonne de choses, mais pourquoi introduire une telle incohérence flagrante?
4
@delnan - "pourquoi" était la question ici.
Henk Holterman
8
@HenkHolterman Et la "cohérence" est la réfutation de votre point "la chaîne pourrait être traitée contrairement aux autres types de référence".
6
@delnan: Travailler sur un langage qui traite la chaîne comme des types de valeur et travailler 2 ans et plus sur dotnet, je suis d'accord avec Henk. Je le vois comme un défaut majeur sur dotnet.
Fabricio Araujo
1
@delnan: On pourrait créer un type de valeur qui se comporte essentiellement comme String, à l'exception de (1) le comportement value-type-ish d'avoir une valeur par défaut utilisable, et (2) une couche supplémentaire malheureuse d'indirection de boxe à chaque fois qu'il est converti en Object. Étant donné que la représentation en tas de stringest unique, avoir un traitement spécial pour éviter la boxe supplémentaire n'aurait pas été très difficile (en fait, être capable de spécifier des comportements de boxe non par défaut serait également une bonne chose pour d'autres types).
supercat
40

Habib a raison - car stringc'est un type de référence.

Mais plus important encore, vous n'avez pas à vérifier nullchaque fois que vous l'utilisez. Vous devriez probablement jeter unArgumentNullException si quelqu'un transmet une nullréférence à votre fonction .

Voici la chose - le framework vous lancerait NullReferenceExceptionde toute façon si vous essayiez d'appeler .ToUpper()une chaîne. N'oubliez pas que ce cas peut toujours se produire même si vous testez vos arguments nullcar toute propriété ou méthode sur les objets passés à votre fonction en tant que paramètres peut être évaluée commenull .

Cela étant dit, la vérification des chaînes vides ou des valeurs nulles est une chose courante à faire, donc elles fournissent String.IsNullOrEmpty()et String.IsNullOrWhiteSpace()à cette fin.

Dave Markle
la source
30
Vous ne devriez jamais jeter un NullReferenceExceptionvous - même ( msdn.microsoft.com/en-us/library/ms173163.aspx ); vous lancez un ArgumentNullExceptionsi votre méthode ne peut pas accepter de références nulles. De plus, les NullRef sont généralement l'une des exceptions les plus difficiles aux diagnostics lorsque vous résolvez des problèmes, donc je ne pense pas que la recommandation de ne pas vérifier la valeur null soit très bonne.
Andy
3
@Andy "Les NullRef sont généralement l'une des exceptions les plus difficiles à diagnostiquer" Je suis fortement en désaccord, si vous enregistrez des trucs, c'est vraiment facile à trouver et à corriger (il suffit de gérer le cas nul).
Louis Kottmann
6
Le lancement ArgumentNullExceptiona l'avantage supplémentaire de pouvoir fournir le nom du paramètre. Pendant le débogage, cela économise ... euh, secondes. Mais des secondes importantes.
Kos
2
@DaveMarkle, vous voudrez peut-être aussi inclure IsNullOrWhitespace msdn.microsoft.com/en-us/library/…
Nathan Koop
1
Je pense vraiment que la vérification de null partout est une source de ballonnement de code immense. c'est moche, et ça a l'air hacky et c'est difficile de rester cohérent. Je pense (au moins dans les langages de type C #) une bonne règle est "bannir le mot-clé null dans le code de production, l'utiliser comme un fou dans le code de test".
sara
24

Vous pouvez écrire une méthode d'extension (pour ce qu'elle vaut):

public static string EmptyNull(this string str)
{
    return str ?? "";
}

Maintenant, cela fonctionne en toute sécurité:

string str = null;
string upper = str.EmptyNull().ToUpper();
Tim Schmelter
la source
100
Mais s'il vous plaît ne le faites pas. La dernière chose qu'un autre programmeur veut voir, ce sont des milliers de lignes de code parsemées de .EmptyNull () partout juste parce que le premier type a "peur" des exceptions.
Dave Markle
15
@DaveMarkle: Mais évidemment, c'est exactement ce que OP recherchait. "C'est assez ennuyeux de tester toutes mes chaînes pour null avant de pouvoir appliquer en toute sécurité des méthodes comme ToUpper (), StartWith () etc"
Tim Schmelter
19
Le commentaire s'adressait au PO, pas à vous. Bien que votre réponse soit clairement correcte, un programmeur posant une question de base comme celle-ci devrait être fortement déconseillé de mettre réellement votre solution en pratique WIDE, comme c'est souvent leur habitude. Il y a un certain nombre de compromis dont vous ne discutez pas dans votre réponse, tels que l'opacité, la complexité accrue, la difficulté de refactorisation, la surutilisation potentielle des méthodes d'extension et oui, les performances. Parfois (plusieurs fois) une bonne réponse n'est pas la bonne voie, et c'est pourquoi j'ai commenté.
Dave Markle
5
@Andy: La solution pour ne pas faire correctement la vérification des null est de vérifier correctement les null, et non de mettre un pansement sur un problème.
Dave Markle
7
Si vous avez du mal à écrire .EmptyNull(), pourquoi ne pas simplement utiliser à la (str ?? "")place là où vous en avez besoin? Cela dit, je suis d'accord avec le sentiment exprimé dans le commentaire de @ DaveMarkle: vous ne devriez probablement pas. nullet String.Emptysont conceptuellement différents, et vous ne pouvez pas nécessairement traiter l'un de la même manière qu'un autre.
un CVn du
17

Vous pouvez également utiliser ce qui suit, à partir de C # 6.0

string myString = null;
string result = myString?.ToUpper();

Le résultat de la chaîne sera nul.

russelrillema
la source
1
Pour être correct, depuis c # 6.0, la version de l'IDE n'a rien à voir avec cela car c'est une fonctionnalité de langue.
Stijn Van Antwerpen
3
Une autre option -public string Name { get; set; } = string.Empty;
Jaja Harris
Comment ça s'appelle? myString? .ToUpper ();
Hunter Nelson
1
Cela s'appelle un opérateur null-conditionnel. Vous pouvez lire à ce sujet ici msdn.microsoft.com/en-us/magazine/dn802602.aspx
russelrillema
14

Les chaînes vides et les valeurs nulles sont fondamentalement différentes. Un null est une absence de valeur et une chaîne vide est une valeur qui est vide.

Le langage de programmation faisant des hypothèses sur la "valeur" d'une variable, dans ce cas une chaîne vide, sera aussi efficace que d'initier la chaîne avec toute autre valeur qui ne causera pas de problème de référence nulle.

De plus, si vous passez le handle de cette variable de chaîne à d'autres parties de l'application, ce code n'aura aucun moyen de valider si vous avez intentionnellement passé une valeur vide ou si vous avez oublié de remplir la valeur de cette variable.

Une autre occasion où ce serait un problème est lorsque la chaîne est une valeur de retour d'une fonction. Étant donné que la chaîne est un type de référence et peut techniquement avoir une valeur nulle et vide à la fois, par conséquent, la fonction peut également retourner techniquement un null ou un vide (rien ne l'empêche de le faire). Maintenant, comme il y a 2 notions d '"absence de valeur", c'est-à-dire une chaîne vide et une valeur nulle, tout le code qui consomme cette fonction devra faire 2 vérifications. Un pour vide et l'autre pour null.

En bref, il est toujours bon de n'avoir qu'une seule représentation pour un seul état. Pour une discussion plus large sur les vides et les null, voir les liens ci-dessous.

/software/32578/sql-empty-string-vs-null-value

NULL vs vide en cas de saisie utilisateur

Nerrve
la source
2
Et comment voyez-vous exactement cette différence, disons dans une zone de texte? L'utilisateur a-t-il oublié de saisir une valeur dans le champ ou le laisse-t-il volontairement vide? Null dans un langage de programmation a une signification spécifique; Non attribué. Nous savons qu'il n'a pas de valeur, ce qui n'est pas la même chose qu'un null de base de données.
Andy
1
il n'y a pas beaucoup de différence lorsque vous l'utilisez avec une zone de texte. Dans les deux cas, avoir une notation pour représenter l'absence d'une valeur dans une chaîne est primordial. Si je devais en choisir un, je choisirais null.
Nerrve
Dans Delphi, la chaîne est un type de valeur et ne peut donc pas être nulle. Cela rend la vie beaucoup plus facile à cet égard - je trouve vraiment très ennuyeux de faire de la chaîne un type de référence.
Fabricio Araujo du
1
Sous le COM (Common Object Model) qui était antérieur au .net, un type de chaîne contiendrait soit un pointeur vers les données de la chaîne, soit nullpour représenter la chaîne vide. Il y avait un certain nombre de façons dont .net aurait pu implémenter une sémantique similaire, s'il avait choisi de le faire, d'autant plus qu'il Stringpossède un certain nombre de caractéristiques qui en font de toute façon un type unique [par exemple, il et les deux types de tableaux sont les seuls types dont l'allocation la taille n'est pas constante].
supercat
7

La raison / le problème fondamental est que les concepteurs de la spécification CLS (qui définit comment les langues interagissent avec .net) n'ont pas défini de moyen par lequel les membres de la classe pourraient spécifier qu'ils doivent être appelés directement, plutôt que viacallvirt , sans que l'appelant effectue une vérification de référence nulle; elle n'a pas non plus permis de définir des structures qui ne seraient pas soumises à une boxe "normale".

Si la spécification CLS avait défini un tel moyen, il serait alors possible pour .net de suivre systématiquement l'exemple établi par le Common Object Model (COM), selon lequel une référence de chaîne nulle était considérée sémantiquement équivalente à une chaîne vide, et pour d'autres types de classes immuables définis par l'utilisateur qui sont censés avoir une sémantique de valeurs pour définir également des valeurs par défaut. Essentiellement, ce qui se passerait serait que chaque membre de String, par exemple, Lengthsoit écrit comme quelque chose comme [InvokableOnNull()] int String Length { get { if (this==null) return 0; else return _Length;} }. Cette approche aurait offert une très bonne sémantique pour les choses qui devraient se comporter comme des valeurs, mais en raison des problèmes d'implémentation, elles doivent être stockées sur le tas. La plus grande difficulté avec cette approche est que la sémantique de conversion entre ces types et Objectpourrait devenir un peu trouble.

Une approche alternative aurait été de permettre la définition de types de structure spéciaux qui n'héritent pas Objectmais qui ont à la place des opérations de boxe et de déballage personnalisées (qui se convertissent vers / depuis un autre type de classe). Dans une telle approche, il y aurait un type de classe NullableStringqui se comporte comme une chaîne maintenant, et un type de structure encadré personnalisé String, qui contiendrait un seul champ privé Valuede type String. Tentative de conversion Stringde a NullableStringou Objectretourne Valuesi non-null ou String.Emptysi null. En tentant de transtyper en String, une référence non nulle à une NullableStringinstance stockerait la référence dans Value(peut-être en stockant null si la longueur était nulle); le fait de jeter toute autre référence entraînerait une exception.

Même si les chaînes doivent être stockées sur le tas, il n'y a théoriquement aucune raison pour qu'elles ne se comportent pas comme des types de valeur qui ont une valeur par défaut non nulle. Les avoir stockés comme une structure "normale" qui contenait une référence aurait été efficace pour le code qui les utilisait comme type "chaîne", mais aurait ajouté une couche supplémentaire d'indirection et d'inefficacité lors de la conversion en "objet". Bien que je ne prévois pas .net d'ajouter l'une des fonctionnalités ci-dessus à cette date tardive, les concepteurs de futurs cadres pourraient peut-être envisager de les inclure.

supercat
la source
1
S'exprimant en tant que personne qui travaille beaucoup en SQL et a traité le casse-tête d'Oracle ne faisant pas de distinction entre NULL et zéro, je suis très heureux que .NET le fasse . "Vide" est une valeur, "null" ne l'est pas.
@ JonofAllTrades: Je ne suis pas d'accord. Sur le code d'application, à l'exception du code db, il n'y a aucune signification qu'une chaîne soit traitée comme une classe. C'est un type de valeur et un type de base. Supercat: +1 pour vous
Fabricio Araujo
1
Le code de base de données est un gros "sauf". Tant qu'il existe des domaines problématiques où vous devez faire la distinction entre "présent / connu, une chaîne vide" et "non présent / inconnu / inapplicable", comme les bases de données, le langage doit alors le prendre en charge. Bien sûr, maintenant que .NET a Nullable<>, les chaînes peuvent être réimplémentées en tant que types de valeur; Je ne peux pas parler des coûts et des avantages d'un tel choix.
3
@JonofAllTrades: Le code qui traite des nombres doit avoir un moyen hors bande de distinguer la valeur par défaut zéro de "non défini". En l'état, le code de gestion nullable qui fonctionne avec les chaînes et les nombres doit utiliser une méthode pour les chaînes nullables et une autre pour les nombres nullables. Même si un type de classe annulable stringest plus efficace qu'il Nullable<string>ne le serait, avoir à utiliser la méthode "plus efficace" est plus contraignant que de pouvoir utiliser la même approche pour toutes les valeurs de base de données annulables.
supercat
5

Parce qu'une variable de chaîne est une référence , pas une instance .

L'initialiser à Vide par défaut aurait été possible mais cela aurait introduit beaucoup d'incohérences sur toute la carte.

Henk Holterman
la source
3
Il n'y a aucune raison particulière de stringdevoir être un type de référence. Bien sûr, les caractères réels qui composent la chaîne doivent certainement être stockés sur le tas, mais étant donné la quantité de support dédié que les chaînes ont déjà dans le CLR, il ne serait pas étirable d'avoir System.Stringun type de valeur avec un champ privé unique Valuede type HeapString. Ce champ serait un type de référence et aurait la valeur par défaut null, mais une Stringstructure dont le Valuechamp était nul se comporterait comme une chaîne vide. Le seul inconvénient de cette approche serait ...
supercat
1
... que la conversion d'un Stringto Objectentraînerait, en l'absence de code de cas particulier lors de l'exécution, la création d'une Stringinstance encadrée sur le tas, plutôt que de simplement copier une référence au HeapString.
supercat
1
@supercat - personne ne dit que la chaîne devrait / pourrait être un type de valeur.
Henk Holterman
1
Personne sauf moi. Le fait que la chaîne soit un type de valeur "spécial" (avec un champ de type référence privé) permettrait à la plupart des manipulations d'être essentiellement aussi efficaces qu'elles le sont actuellement, à l'exception d'une vérification nulle supplémentaire sur les méthodes / propriétés comme .Lengthetc. de sorte que les instances qui détiennent une référence nulle n'essaierait pas de le déréférencer mais se comporterait à la place comme approprié pour une chaîne vide. Si le Framework serait meilleur ou pire avec une stringimplémentation de cette façon, si l'on voulait default(string)être une chaîne vide ...
supercat
1
... avoir stringun wrapper de type valeur sur un champ de type référence serait l'approche qui nécessiterait le moins de modifications dans les autres parties de .net [en effet, si l'on était prêt à accepter la conversion de Stringpour Objectcréer un élément supplémentaire, on aurait pu simplement Stringêtre une structure ordinaire avec un champ de type Char[]qu'elle n'a jamais exposé]. Je pense qu'avoir un HeapStringtype serait probablement mieux, mais à certains égards, la chaîne de type valeur contenant un Char[]serait plus simple.
supercat
5

Pourquoi les concepteurs de C # ont choisi d'utiliser null comme valeur par défaut des chaînes?

Étant donné que les chaînes sont des types de référence , les types de référence sont la valeur par défaut est null. Les variables des types de référence stockent des références aux données réelles.

Utilisons un defaultmot-clé pour ce cas;

string str = default(string); 

strest un string, donc c'est un type de référence , donc la valeur par défaut est null.

int str = (default)(int);

strest un int, c'est donc un type de valeur , donc la valeur par défaut est zero.

Soner Gönül
la source
4

Si la valeur par défaut de stringétait la chaîne vide, je n'aurais pas à tester

Faux! La modification de la valeur par défaut ne change pas le fait qu'il s'agit d'un type de référence et que quelqu'un peut toujours définir explicitement la référence comme étant null.

De plus, cela Nullable<String>aurait du sens.

Vrai point. Il serait plus logique de ne pas autoriser nullde types de référence, au lieu d'exiger Nullable<TheRefType>cette fonctionnalité.

Alors pourquoi les concepteurs de C # ont-ils choisi d'utiliser nullcomme valeur par défaut des chaînes?

Cohérence avec d'autres types de référence. Maintenant, pourquoi autoriser nullles types de référence? Probablement pour qu'il ressemble à C, même si c'est une décision de conception discutable dans un langage qui fournit également Nullable.

Dan Burton
la source
3
Cela pourrait être dû au fait que Nullable n'a été introduit que dans le .NET 2.0 Framework, donc avant cela, il n'était pas disponible?
jcolebrand
3
Merci Dan Burton d'avoir souligné que quelqu'un PEUT définir la valeur initialisée sur null sur les types de référence plus tard. Penser à travers cela me dit que mon intention initiale dans la question ne sert à rien.
Marcel
4

Peut-être que si vous utilisiez l' ??opérateur lors de l'attribution de votre variable de chaîne, cela pourrait vous aider.

string str = SomeMethodThatReturnsaString() ?? "";
// if SomeMethodThatReturnsaString() returns a null value, "" is assigned to str.
Amen Jlili
la source
2

Une chaîne est un objet immuable, ce qui signifie que lorsqu'elle reçoit une valeur, l'ancienne valeur n'est pas effacée de la mémoire, mais reste à l'ancien emplacement et la nouvelle valeur est placée dans un nouvel emplacement. Donc, si la valeur par défaut de String aétait String.Empty, cela gaspillerait laString.Empty bloc en mémoire quand on sa première valeur.

Bien que cela semble minuscule, cela pourrait devenir un problème lors de l'initialisation d'un grand tableau de chaînes avec des valeurs par défaut de String.Empty. Bien sûr, vous pouvez toujours utiliser la StringBuilderclasse mutable si cela devait être un problème.

djv
la source
Merci d'avoir mentionné la chose "première initialisation".
Marcel
3
Comment serait-ce un problème lors de l'initialisation d'un grand tableau? Comme, comme vous l'avez dit, les chaînes sont immuables, tous les éléments du tableau seraient simplement des pointeurs vers le même String.Empty. Suis-je trompé?
Dan Burton
2
La valeur par défaut pour tout type va avoir tous les bits mis à zéro. Le seul moyen pour que la valeur par défaut de stringsoit une chaîne vide est d'autoriser tous les bits à zéro comme représentation d'une chaîne vide. Il y a plusieurs façons d'y parvenir, mais je ne pense pas que cela implique d'initialiser les références à String.Empty.
supercat
D'autres réponses ont également discuté de ce point. Je pense que les gens ont conclu qu'il ne serait pas logique de traiter la classe String comme un cas spécial et de fournir autre chose que all-bits-zero comme initialisation, même si c'était quelque chose comme String.Emptyou "".
djv
@DanV: Changer le comportement d'initialisation des stringemplacements de stockage aurait nécessité également de changer le comportement d'initialisation de toutes les structures ou classes qui ont des champs de type string. Cela représenterait un assez grand changement dans la conception de .net, qui prévoit actuellement d'initialiser à zéro n'importe quel type sans même avoir à penser à ce qu'il est, sauf pour sa taille totale.
supercat
2

La chaîne étant un type de référence et la valeur par défaut du type de référence est null.

Akshay
la source
0

Peut-être que le stringmot clé vous a dérouté, car il ressemble exactement à toute autre déclaration de type de valeur , mais il s'agit en fait d'un alias System.Stringcomme expliqué dans cette question .
De plus, la couleur bleu foncé dans Visual Studio et la première lettre minuscule peuvent induire en erreur en pensant qu'il s'agit d'un struct.

Alessandro Da Rugna
la source
3
N'est-ce pas la même chose pour le objectmot clé? Certes, c'est beaucoup moins utilisé que string.
2
Comme intc'est un alias pour System.Int32. À quoi veux-tu en venir? :)
Thorarin
@Thorari @delnan: Ce sont tous les deux des alias, mais System.Int32c'est Structdonc un ayant une valeur par défaut tandis System.Stringqu'un a Classun pointeur avec une valeur par défaut de null. Ils sont visuellement présentés dans la même police / couleur. Sans connaissance, on peut penser qu'ils agissent de la même manière (= avoir une valeur par défaut). Ma réponse a été écrite avec une idée de psychologie cognitive en.wikipedia.org/wiki/Cognitive_psychology derrière elle :-)
Alessandro Da Rugna
Je suis à peu près certain qu'Anders Hejlsberg l'a dit dans une interview sur la chaîne 9. Je connais la différence entre le tas et la pile, mais l'idée avec C # est que le programmeur occasionnel n'en a pas besoin.
Thomas Koelle
0

Les types nullables ne sont entrés qu'en 2.0.

Si des types nullables avaient été créés au début du langage, alors la chaîne aurait été non nullable et la chaîne? aurait été annulable. Mais ils ne pouvaient pas faire cela du à la compatibilité descendante.

Beaucoup de gens parlent de type ref ou non de type ref, mais la chaîne est une classe hors du commun et des solutions auraient été trouvées pour le rendre possible.

Thomas Koelle
la source