2017-01-30 2 views
29

je suis tombé sur une étrange manière de mettre en œuvre ToString() et je me demande comment cela fonctionne:Comment int + chaîne devient une chaîne?

public string tostr(int n) 
{ 
    string s = ""; 
    foreach (char c in n-- + "") { //<------HOW IS THIS POSSIBLE ? 
     s = s + c; 
    } 
    return s; 
} 

Est-ce le iterator en supposant que la taille d'un char?

+17

J'espère que ce n'est pas le code de production ... –

+0

Essayez ceci http://stackoverflow.com/questions/17575375/how-do-i-convert-an-int-to-a-string -in-c-sharp-without-using-tostring –

+0

Ceci est une astuce courante de Code Golf, il ne devrait pas être utilisé dans le code de production parce que, comme vous l'avez vu, cela mène à la confusion – TheLethalCoder

Répondre

21

Il appelle la méthode String.Concat(object, object) implicitement, ce qui concaténer les représentations de chaînes de deux objets spécifiés :

string result = String.Concat("", n--); 

La méthode String.Concat(object, object) appelle ensuite String.Concat(string, string). Pour lire la source Concat et vérifier en profondeur, d'abord allez ici: puis dans cette page dans la recherche TextBox tapez String puis cliquez sur le lien String.cs dans les résultats pour aller au code source String.cs dans C# .NET page et vérifiez la méthode Concat.

Telle est la définition de la méthode:

public static String Concat(Object arg0, Object arg1) 
{ 
    Contract.Ensures(Contract.Result<string>() != null); 
    Contract.EndContractBlock(); 

    if (arg0 == null) 
    { 
     arg0 = String.Empty; 
    } 

    if (arg1==null) 
    { 
     arg1 = String.Empty; 
    } 
    return Concat(arg0.ToString(), arg1.ToString()); 
} 

Comme vous le voyez ce appelle la méthode public static String Concat(String str0, String str1) enfin:

public static String Concat(String str0, String str1) 
{ 
    Contract.Ensures(Contract.Result<string>() != null); 
    Contract.Ensures(Contract.Result<string>().Length == 
     (str0 == null ? 0 : str0.Length) + 
     (str1 == null ? 0 : str1.Length)); 
    Contract.EndContractBlock(); 

    if (IsNullOrEmpty(str0)) { 
     if (IsNullOrEmpty(str1)) { 
      return String.Empty; 
     } 
     return str1; 
    } 

    if (IsNullOrEmpty(str1)) { 
     return str0; 
    } 

    int str0Length = str0.Length; 

    String result = FastAllocateString(str0Length + str1.Length); 

    FillStringChecked(result, 0,  str0); 
    FillStringChecked(result, str0Length, str1); 

    return result; 
} 

, ce qui est sous-jacent IL, par Ildasm:

.method public hidebysig instance string 
     tostr(int32 n) cil managed 
{ 
    // Code size  74 (0x4a) 
    .maxstack 3 
    .locals init ([0] string s, 
      [1] string V_1, 
      [2] int32 V_2, 
      [3] char c, 
      [4] string V_4) 
    IL_0000: nop 
    IL_0001: ldstr  "" 
    IL_0006: stloc.0 
    IL_0007: nop 
    IL_0008: ldarg.1 
    IL_0009: dup 
    IL_000a: ldc.i4.1 
    IL_000b: sub 
    IL_000c: starg.s n 
    IL_000e: box  [mscorlib]System.Int32 
    IL_0013: call  string [mscorlib]System.String::Concat(object) 
    IL_0018: stloc.1 
    IL_0019: ldc.i4.0 
    IL_001a: stloc.2 
    IL_001b: br.s  IL_0039 
    IL_001d: ldloc.1 
    IL_001e: ldloc.2 
    IL_001f: callvirt instance char [mscorlib]System.String::get_Chars(int32) 
    IL_0024: stloc.3 
    IL_0025: nop 
    IL_0026: ldloc.0 
    IL_0027: ldloca.s c 
    IL_0029: call  instance string [mscorlib]System.Char::ToString() 
    IL_002e: call  string [mscorlib]System.String::Concat(string, 
                   string) 
    IL_0033: stloc.0 
    IL_0034: nop 
    IL_0035: ldloc.2 
    IL_0036: ldc.i4.1 
    IL_0037: add 
    IL_0038: stloc.2 
    IL_0039: ldloc.2 
    IL_003a: ldloc.1 
    IL_003b: callvirt instance int32 [mscorlib]System.String::get_Length() 
    IL_0040: blt.s  IL_001d 
    IL_0042: ldloc.0 
    IL_0043: stloc.s V_4 
    IL_0045: br.s  IL_0047 
    IL_0047: ldloc.s V_4 
    IL_0049: ret 
}// end of method tostr 
+2

J'ai toujours considéré la "magie" derrière l'opérateur de chaîne '+' comme un mauvais design. L'opérateur n'existe pas vraiment, le compilateur fait un peu de magie et appelle 'Concat'. Je trouve loin d'être intuitif que '1 + s' devrait implicitement convertir un nombre en une chaîne, c'est un problème facile à aborder, spécialement pour les programmeurs novices. 'string.Concat (1, s)' aurait dû être la seule option, et tout ce qui n'est pas 'string + string' aurait dû être une erreur de compilation. – InBetween

+0

@InBetween Que signifie pour un opérateur «exister»? Si '1 + 1' appelait (hypothétiquement)' Number.Add (1, 1) ', cela serait-il un mauvais design? – immibis

+0

@immibis cela signifie qu'il n'y a pas d'opérateur '+ 'défini dans' string'.Le compilateur génère un code spécifique pour la concaténation de chaîne via '+', ce n'est pas une surcharge implémentée dans la classe. – InBetween

10

Le type de n-- est int, qui est converti en string en utilisant + pour le concaténer avec "", qui est de type string. En outre, string implémente IEnumerable<char>, sur laquelle l'itération réelle avec foreach a lieu.

15

Expliquer cette « étape par étape »:

// assume the input is 1337 
public string tostr(int n) { 
    //line below is creating a placeholder for the result string 
    string s = ""; 
    // below line we can split into 2 lines to explain in more detail: 
    // foreach (char c in n-- + "") { 
    // then value of n is concatenated with an empty string : 
    // string numberString = n-- + ""; // numberString is "1337"; 
    // and after this line value of n will be 1336 
    // which then is iterated though : 
    // foreach(char c in numberString) { // meaning foreach(char c in "1337") 
    foreach (char c in n-- + "") { //<------HOW IS THIS POSSIBLE ? 
     s = s + c; // here each sign of the numberString is added into the placeholder 
    } 
    return s; // return filled placeholder 
} 

Donc, fondamentalement, si vous concaténer string avec int il sera automatiquement appeler la méthode int.ToString et se joindre à la chaîne ensemble.

+6

Remarque 'n -' est un post-décrémentation, donc 'n' est converti en une chaîne avec' n + "" 'et est ensuite décrémenté. – TheLethalCoder

+0

@TheLethalCoder désolé, mon erreur. Correction de ceci dans la réponse mise à jour. –

5

Ce code semble incompréhensible parce que c'est le résultat de ce que je considère comme un choix de conception horrible dans la langue. L'opérateur + n'existe pas réellement dans string. Si vous regardez la source de référence, ou MSDN page, les seuls opérateurs déclarés pour string sont == et !=.

Ce qui se passe réellement, c'est que le compilateur tire l'un de ses tours de magie, et convertit l'opérateur + en un appel à la méthode statique string.Concat.

Maintenant, si vous est arrivé de rencontrer foreach (char c in string.Concat(n--, "")) vous auriez probablement comprendre le code beaucoup mieux, parce que l'intention est claire : Je veux concaténer deux objets sous forme de chaînes, puis énumérer les char s qui composent la chaîne résultante.

Quand vous lisez n-- + "" cette intention est loin d'être clair, et son pire si vous arrive d'avoir n-- + s (s étant un string).

Le compilateur dans les deux cas décide que vous souhaitez concaténer les arguments en tant que chaînes et mappe automatiquement cet appel à string.Concat(object, object). Un des locataires de C# est que, à moins que l'intention du codeur ne soit claire, agitez un drapeau rouge et demandez au codeur de clarifier son intention. Dans ce cas particulier, ce locataire est complètement violé.

À mon humble avis, tout ce qui n'est pas un string + string aurait dû être une erreur de compilation, mais ce train a été transmis il y a de nombreuses années.

+2

De retour dans le bon vieux temps - "l'âge d'or" des ordinateurs - aweful signifiait génial. –

+0

Il y a une étape de plus que vous évitez, c'est que les deux paramètres de Concat obtiennent leur ToString appelée, ainsi le n-- deviendra (n -). ToString() –

+1

Il est intéressant de noter que le compilateur combine une longue série des opérations "+" sur les chaînes en un seul appel Concat. On peut supposer que l'équipe de conception a vu que c'était une manière très commune de développeurs construisant des chaînes. S'il était implémenté en tant qu'opérateur standard, il aurait besoin d'effectuer chaque Concat séparément, ce qui est un gaspillage car il crée un tas d'instances de chaînes intermédiaires. On peut dire qu'il ne devrait plus exister à cause de l'interpolation de la chaîne, mais il sera maintenant disponible pour la rétrocompatibilité. –

0

J'ai utilisé ReSharper pour convertir la fonction en instructions Linq, ce qui peut aider certaines personnes à comprendre ce qui se passe (ou simplement confondre les gens).

public string tostrLinq(int n) 
{ 
    return string.Concat(n--, "").Aggregate("", (string current, char c) => current + c); 
} 

Comme d'autres ont déjà dit, au fond de la int est entrée concated avec un vide string qui vous donne essentiellement une représentation de chaîne de la int. Comme string implémente IEnumberable, la boucle foreach décompose la chaîne en char[], en donnant chaque caractère de la chaîne à chaque itération. Le corps de la boucle réunit ensuite les caractères ensemble en tant que chaîne en concaténant chaque char.

Ainsi, par exemple, entrée donné 5423, il est converti en "5423", puis divisé en "5", "4", "2", "3" et enfin piquées retour à "5423".

Maintenant la partie qui m'a vraiment fait mal pendant un moment était le n--. Si cela décrémente le int, alors pourquoi ne recevons-nous pas "5422" à la place? Cela me n'a pas été clair qu'après avoir lu les MSDN Article: Increment (++) and Decrement (--) Operators

L'incrément et les opérateurs décrémentation sont utilisés comme raccourci pour modifier la valeur stockée dans une variable et accéder à cette valeur. L'opérateur peut être utilisé dans une syntaxe de préfixe ou de suffixe.

If   | Equivalent Action | Return value 
===================================== 
++variable | variable += 1  | value of variable after incrementing 
variable++ | variable += 1  | value of variable before incrementing 
--variable | variable -= 1  | value of variable after decrementing 
variable-- | variable -= 1  | value of variable before decrementing 

donc parce que l'opérateur de décrémentation est appliqué à la fin de n, la valeur de n est lu et utilisé par string.Concat avant n est décrémenté par 1.

à savoir string.Concat(n--,"") va donner la même sortie que string.Contact(n, ""); n = n - 1;.

Donc, pour obtenir "5422" nous changerions à string.Concat(--n, "") de sorte que n est décrément première avant qu'il ne soit transmis à string.Contact.

TL; DR; La fonction est une manière détournée de faire n.ToString()

Fait intéressant, j'ai aussi utilisé ReSharper pour le convertir en une boucle for mais la fonction ne fonctionne plus comme n est décrémenté à chaque itération de la boucle, contrairement à la boucle foreach :

public string tostrFor(int n) 
{ 
    string s = ""; 
    for (int index = 0; index < string.Concat(n--, "").Length; index++) 
    { 
     char c = string.Concat(n--, "")[index]; 
     s = s + c; 
    } 
    return s; 
} 
1

Décomposer votre code pour montrer pourquoi il arrive ...

foreach(char c in n-- + "") 

utilisant le + opérateur avec une chaîne comme l'un des opérandes convertira le résultat en une chaîne indépendamment de ce que l'autre opérande primitif était, tant qu'il avait une implémentation de +. Ce faisant, n participe à la méthode string.Concat comme vous pouvez le voir sur la capture d'écran ci-dessous de la fenêtre de débogage ... Autos

Autos window showing identifier values

J'ai appelé la méthode avec « 34 » comme vous pouvez déduire. L'identificateur de boucle est défini comme char c et passe par une chaîne. Cela fonctionne parce que string met en œuvre IEnumerable<char> comme vous pouvez le voir sur la capture d'écran du résultat de « Aller à la définition » du type de chaîne:

Result of right clicking string type and going to definition

De ce point, il fonctionne comme si vous étiez itérez à travers toute autre liste/tableau ... ou plus précisément, IEnumerable avec foreach et obtient chaque individu char. n dans le même temps a été changé en n-- // 33 in my case. À ce stade, la valeur de n est sans conséquence puisque vous parcourez le résultat de l'expression n-- + "". Il aurait aussi bien pu être n++ et vous obtiendrez le même résultat.

La ligne s = s + c; devrait être assez explicite et juste au cas où il n'est pas, chaque carboniser vous tirez du résultat de chaîne temporaire de n-- + "" est ajouté à votre vide (au début) string s = "";. Il en résulte une chaîne car comme mentionné précédemment + avec une chaîne impliquée entraînera une chaîne. Une fois que vous avez fini de parcourir tous les caractères, il retourne la représentation sous forme de chaîne.

+0

La partie sur 'char' étant implicitement convertible en' int' est complètement hors de propos ici. Le code convertit un 'int' en' string' puis itère sur les caractères ... à aucun moment aucun 'char' n'est implicitement converti en' int'. Les autres réponses sont plus claires, OMI. –

+0

@JonSkeet merci pour les commentaires. J'ai enlevé la section sur char/int. Je pensais que cela aurait été utile puisque j'allais aussi parler de la chaîne étant un 'IEnumerable ' – Fabulous

0

concaténation de chaîne:

… 
string operator +(object x, string y); 

Le + binaire opérateur effectue la concaténation de chaîne lorsqu'un ou les deux opérandes sont de type chaîne. Si un opérande de concaténation de chaîne est nul, une chaîne vide est substituée. Sinon, tout opérande non-chaîne est converti en sa représentation sous forme de chaîne en appelant la méthode virtuelle ToString héritée du type object. Si ToString renvoie null, une chaîne vide est substituée. - ECMA-334 page 201.

Ainsi, n.ToString() est appelé.Tout le reste de la méthode est juste en train de décomposer et de recomposer le résultat sans aucun effet.

Il aurait pu être écrit:

public string tostr(int n) => n.ToString(); 

Mais, pourquoi?

0
n-- + "" 

est le même que

n + "" 

parce que nous ne pas utiliser la valeur de n plus tard

n + "" 

est le même que

n.ToString() 

parce opérateur + Utilisé avec int et string convertit int en string, si

foreach (char c in n-- + "") 

est le même que

foreach (char c in n.ToString()) 

le reste est simple.

0

Puisque n-- est une post-décrémentation, la valeur de n n'est modifiée qu'après la concaténation. donc essentiellement, il ne fait rien du tout. il aurait pu tout simplement été

foreach (char c in n + "") 

ou

foreach (char c in (n++) + "") 

soit rien change rien. la valeur d'origine est itérée