Pretending chaînes .NET sont le type de valeur
-
11-09-2019 - |
Question
Dans .NET, les chaînes sont immuables et sont des variables de type référence. Cela vient souvent comme une surprise pour les nouveaux développeurs .NET qui peuvent les confondre avec des objets de type de valeur en raison de leur comportement. Cependant, autre que la pratique de l'utilisation StringBuilder
longtemps concaténation esp. en boucle, est-il une raison quelconque dans la pratique que l'on a besoin de savoir cette distinction?
Quels sont les scénarios du monde réel sont aidés ou évités par la compréhension de la distinction la valeur de référence en ce qui concerne les chaînes .NET par rapport à simplement faire semblant / les comprennent mal à être les types de valeur?
La solution
La conception de string
s était délibérément de telle sorte que vous ne devriez pas avoir trop de soucis à ce sujet en tant que programmeur. Dans de nombreux cas, cela signifie que vous pouvez simplement attribuer, déplacer, copier, modifier les chaînes sans trop penser aux conséquences complexes possibles si une autre référence à votre chaîne existait et serait changé en même temps (comme cela se produit avec des références d'objets).
Paramètres de chaîne dans un appel de méthode
(EDIT: cette section a ajouté plus tard)
Lorsque les chaînes sont transmises à une méthode, ils sont passés par référence. Quand ils ne sont lus dans le corps de la méthode, rien ne se passe spécial. Mais quand ils sont changés, une copie est créée et la variable temporaire est utilisée dans le reste de la méthode. Ce processus est appelé copy-on-write .
Qu'avez-juniors est qu'ils sont habitués au fait que les objets sont des références et ils sont modifiés dans une méthode qui change le paramètre passé. Pour faire la même chose avec des cordes, ils ont besoin d'utiliser le mot-clé ref
. Cela permet en fait la référence de chaîne à modifier et retourné à la fonction appelante. Si vous ne le faites pas, la chaîne ne peut pas être modifiée par le corps de la méthode:
void ChangeBad(string s) { s = "hello world"; }
void ChangeGood(ref string s) { s = "hello world"; }
// in calling method:
string s1 = "hi";
ChangeBad(s1); // s1 remains "hi" on return, this is often confusing
ChangeGood(ref s1); // s1 changes to "hello world" on return
Sur StringBuilder
Cette distinction est importante, mais les programmeurs débutants sont généralement mieux de ne pas trop savoir à ce sujet. En utilisant StringBuilder
lorsque vous faites beaucoup de chaîne « bâtiment » est bon, mais souvent, votre application aura beaucoup plus de poissons à frire et le petit gain de performance de StringBuilder
est négligeable. Méfiez-vous des programmeurs qui vous indiquent que toutes les manipulations de chaîne devrait être fait en utilisant StringBuilder.
En règle générale très approximative du pouce: StringBuilder a un coût de création, mais appending est pas cher. Chaîne a un coût de création pas cher, mais concaténation est relativement cher. Le point tournant est autour de 400-500 concaténations, selon la taille: après, StringBuilder devient plus efficace
.En savoir plus sur StringBuilder vs performance chaîne
EDIT: basé sur un commentaire de Konrad Rudolph, j'ai ajouté cette section
.Si la règle précédente de base se demander, considérer les points suivants des explications un peu plus détaillées:
- StringBuilder avec de nombreuses petites chaînes concaténation de chaînes ajoute vainc assez rapidement (30, 50 ajouter ses), mais 2μs, même 100% de gain de performance est souvent négligeable (sans danger pour certaines situations rares);
- StringBuilder avec quelques grandes chaînes de y ajouter ses (80 caractères ou des chaînes plus grandes) vainc concaténation de chaînes seulement après des milliers, parfois centièmes de milliers d'itérations et la différence est souvent seulement quelques pourcents;
- Le mélange des actions à cordes (remplacer, insérer, sous-chaîne, regex etc.) fait souvent à l'aide StringBuilder ou concaténation de chaîne égale;
- Chaîne concaténation de constantes peut être optimisée à une distance par le compilateur, le CLR ou le JIT, il ne peut pas pour StringBuilder;
- Code de mélange souvent concaténation
+
,StringBuilder.Append
,String.Format
,ToString
et d'autres opérations de chaîne, en utilisant StringBuilder dans de tels cas est rarement efficace.
Alors, quand il efficace? Dans les cas où de nombreuses petites chaînes sont ajoutées, à savoir, pour sérialiser les données dans un fichier, par exemple, et lorsque vous n'avez pas besoin de changer les données « écrites » une fois « écrite » à StringBuilder. Et dans les cas où de nombreuses méthodes ont besoin d'ajouter quelque chose, parce que StringBuilder est un type de référence et les chaînes sont copiés quand ils sont modifiés.
Sur les chaînes internées
Un problème monte - non seulement avec les programmeurs juniors - quand ils essaient de faire une comparaison de référence et savoir que, parfois, le résultat est vrai, et parfois il est faux, en apparence les mêmes situations. Qu'est-il arrivé? Lorsque les chaînes ont été internées par le compilateur et ajoutés au pool statique global interné de chaînes, comparaison entredeux chaînes peuvent pointer vers la même adresse mémoire. Quand (référence!) Comparant deux chaînes égales, un interné et un non, donnera de faux. Utilisez comparaison de =
ou Equals
et ne jouez pas avec ReferenceEquals
lorsqu'ils traitent avec des chaînes.
Sur String.Empty
Dans la même ligue correspond à un comportement étrange qui se produit parfois lors de l'utilisation String.Empty
: l'String.Empty
statique est toujours interné, mais une variable avec une valeur attribuée est non. Toutefois, par défaut le compilateur attribue String.Empty
et le point à la même adresse de mémoire. Résultat:. Une variable de chaîne mutable, par rapport à ReferenceEquals
, retourne vrai, alors que vous pourriez attendre de faux au lieu
// emptiness is treated differently:
string empty1 = String.Empty;
string empty2 = "";
string nonEmpty1 = "something";
string nonEmpty2 = "something";
// yields false (debug) true (release)
bool compareNonEmpty = object.ReferenceEquals(nonEmpty1, nonEmpty2);
// yields true (debug) false (release, depends on .NET version and how it's assigned)
bool compareEmpty = object.ReferenceEquals(empty1, empty2);
En profondeur
Vous essentiellement posé des questions sur ce que les situations peuvent se produire aux non-initiés. Je pense que mon point se résume à éviter object.ReferenceEquals
parce qu'il ne peut pas faire confiance quand il est utilisé avec des chaînes. La raison est que interner de chaîne est utilisée lorsque la chaîne est constante dans le code, mais pas toujours. Vous ne pouvez pas compter sur ce comportement. Bien que String.Empty
et ""
sont toujours internées, ce n'est pas lorsque le compilateur considère que la valeur peut être modifiée. Différentes options d'optimisation (de débogage vs libération et d'autres) vont donner des résultats différents.
faire vous avez besoin ReferenceEquals
de toute façon? Avec des objets, il est logique, mais avec des cordes, il ne fonctionne pas. Enseigner tous ceux qui travaillent avec des cordes pour éviter son utilisation à moins qu'ils comprennent aussi unsafe
et épinglés objets.
Performance
Quand la performance est importante, vous pouvez savoir que les chaînes sont en fait pas immuable et que en utilisant StringBuilder
est pas toujours approche la plus rapide.
Beaucoup des informations que j'utilisé ici est le noreferrer"> de détail , avec un "comment" pour manipuler la chaîne en place (chaînes transformables).
Mise à jour: a ajouté un exemple de code
Mise à jour: ajouté 'en profondeur' section (espoir que quelqu'un trouve cela utile;)
Mise à jour: a ajouté quelques liens, a ajouté la section sur la chaîne params
Mise à jour: a ajouté l'estimation pour le moment de passer de chaînes à stringbuilder
Mise à jour: a ajouté une section supplémentaire sur StringBuilder vs performances String, après une remarque de Konrad Rudolph
Autres conseils
La seule distinction qui compte vraiment pour la plupart du code est le fait que null
peut être affectée à des variables de chaîne.
Une classe immuable agit comme un type de valeur dans toutes les situations courantes, et vous pouvez faire beaucoup de programmation sans se soucier beaucoup de la différence.
Il est quand vous creusez un peu plus profond et se soucient de la performance que vous avez l'utilisation réelle de la distinction. Par exemple, pour savoir que, bien que le passage d'une chaîne comme un paramètre à une méthode agit comme si une copie de la chaîne est créée, la copie ne prend pas réellement. Cela pourrait être une surprise pour les gens habitués aux langues où les chaînes sont en fait des types de valeur (comme VB6?), Et en passant beaucoup de chaînes en tant que paramètres ne serait pas bon pour la performance.
chaîne est une race spéciale. Ils sont de type référence encore utilisé par la plupart des codeurs comme un type de valeur. En rendant immuable et en utilisant la piscine interne, il optimise l'utilisation de la mémoire qui sera énorme si elle est un type pur de valeur.
Plus de lectures ici:
C # objet String .NET est vraiment par référence? sur
SO
String.intern Méthode sur MSDN
string (référence C #) sur MSDN
Mise à jour:
S'il vous plaît se référer au commentaire de abel
à ce poste. Il corrige ma déclaration trompeuse.