Je suis tombé sur une étrange façon de mettre en œuvre ToString()
et je me demande comment cela fonctionne:
public string tostr(int n)
{
string s = "";
foreach (char c in n-- + "") { //<------HOW IS THIS POSSIBLE ?
s = s + c;
}
return s;
}
L'itérateur prend-il la taille d'une char
?
Il appelle implicitement la méthode String.Concat(object, object)
, qui concatène les représentations sous forme de chaîne 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 le source Concat
et le vérifier en profondeur, allez d'abord ici: Code source String.cs en C # .NET puis dans cette page de la recherche TextBox
tapez String
puis cliquez sur le lien String.cs
dans les résultats accédez à la page code source de String.cs en C # .NET et vérifiez la méthode Concat
.
C'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, la méthode public static String Concat(String str0, String str1)
appelle finalement:
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;
}
Et voici l’IL sous-jacent, 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
Expliquant ceci "pas à pas":
// 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énez string
avec int
, il appellera automatiquement la méthode int.ToString
et joindra la chaîne.
Le type de n--
est int
, qui est converti en string
en utilisant +
pour le concaténer avec ""
, qui est du type string
. De plus, string
implémente IEnumerable<char>
, sur lequel l'itération réelle avec foreach
a lieu.
Ce code semble incompréhensible parce que c'est le résultat de ce que je considère comme un choix de conception épouvantable dans le langage.
L'opérateur +
n'existe pas vraiment dans string
. Si vous examinez la source de référence ou la page MSDN , les seuls opérateurs déclarés pour string
sont ==
et !=
.
En réalité, 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 rencontriez foreach (char c in string.Concat(n--, ""))
, vous comprendriez probablement mieux le code, car l'intention est clear: je veux concaténer deux objets en tant que chaînes, puis énumérer les char
s qui composent la chaîne résultante.
Lorsque vous lisez n-- + ""
, cette intention est loin d'être claire et encore pire si vous avez n-- + s
(s
étant string
).
Dans les deux cas, le compilateur décide que vous souhaitez concaténer les arguments sous forme de chaînes et mappe automatiquement cet appel à string.Concat(object, object)
. L'un des locataires de C # est que, à moins que l'intention du codeur ne soit claire, agite un drapeau rouge et demande au codeur de clarifier son intention. Dans ce cas particulier, ce locataire est violé complètement.
IMHO, tout ce qui n'est pas un string + string
aurait dû être une erreur de compilation, mais ce train est passé depuis de nombreuses années.
Pour décomposer votre code et montrer pourquoi cela se produit ...
foreach(char c in n-- + "")
utiliser l'opérateur +
avec une chaîne comme l'un des opérandes convertira le résultat en chaîne, quel que soit ce que fut l'opérande primitif, à condition qu'il ait une implémentation de +. Ce faisant, n participe à la méthode string.Concat
comme vous pouvez le voir sur la capture d'écran suivante de la fenêtre de débogage d'Autos ...
J'ai appelé la méthode avec "34" comme vous pouvez en déduire. L'identifiant de la boucle est défini comme char c
et traverse une chaîne. Cela fonctionne car string
implémente 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:
Ainsi, à partir de ce moment, cela fonctionne de la même manière que si vous parcouriez une autre liste/un tableau ... ou plus précisément, IEnumerable avec foreach
et obteniez chaque individu char
. n
entre-temps a été remplacé par n-- // 33 in my case
. À ce stade, la valeur de n
est sans conséquence puisque vous effectuez une itération dans le résultat de l'expression n-- + ""
. Cela pourrait aussi bien être n++
et vous obtiendriez le même résultat.
La ligne s = s + c;
doit être assez explicite et, dans le cas contraire, chaque caractère extrait de la chaîne temporaire résultat n-- + ""
est ajouté à votre string s = "";
vide (au début). 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 terminé de parcourir tous les caractères, la représentation sous forme de chaîne est renvoyée.
J'ai utilisé ReSharper pour convertir la fonction en instructions Linq, ce qui peut aider certaines personnes à comprendre ce qui se passe (ou tout simplement dérouter davantage les gens).
public string tostrLinq(int n)
{
return string.Concat(n--, "").Aggregate("", (string current, char c) => current + c);
}
Comme d'autres l'ont déjà indiqué, la variable int
saisie est concaténée avec une variable string
vide qui vous donne essentiellement une représentation sous forme de chaîne de la variable int
. Comme string
implémente IEnumberable
, la boucle foreach
divise la chaîne en un char[]
, donnant chaque caractère de la chaîne à chaque itération. Le corps de la boucle joint alors les caractères en une chaîne en concaténant chaque char
.
Ainsi, par exemple, étant donné l'entrée de 5423
, il est converti en "5423"
, puis divisé en "5"
, "4"
, "2"
, "3"
et finalement réassemblé en "5423"
.
Maintenant, la partie qui m'a vraiment fait mal à la tête pendant un moment était le n--
. Si cela décrémente la int
, pourquoi ne pas renvoyer "5422"
à la place? Ce n’était clair pour moi qu’après avoir lu les article MSDN: opérateurs Increment (++) et Decrement (-)
Les opérateurs d'incrémentation et de décrémentation sont utilisés comme raccourci pour modifier la valeur stockée dans une variable et accéder à cette valeur. Soit opérateur peut être utilisé dans une syntaxe préfixe ou postfixe.
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
Ainsi, étant donné que l'opérateur de décrémentation est appliqué à la fin de n
, la valeur de n
est lue et utilisée par string.Concat
avant que n
ne soit décrémenté de 1.
C'est à dire. string.Concat(n--,"")
va donner le même résultat que string.Contact(n, ""); n = n - 1;
.
Donc, pour obtenir "5422"
, nous le changerions en string.Concat(--n, "")
afin que n
soit décrémenté avant d’être passé à string.Contact
.
TL; DR; La fonction est une façon de faire n.ToString()
Fait intéressant, j’ai également utilisé ReSharper pour la convertir en une boucle for
, mais la fonction ne fonctionne plus car n
est décrémenté à chaque itération de la boucle for, 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;
}
n-- + ""
est le même que
n + ""
parce que nous n'utilisons pas la valeur de n plus tard
n + ""
est le même que
n.ToString()
parce que l'opérateur + utilisé avec int et string convertit int en string, donc
foreach (char c in n-- + "")
est le même que
foreach (char c in n.ToString())
le reste est simple.
Puisque n--
est un post décrément, la valeur de n n’est modifiée qu’après la concaténation. donc essentiellement, il ne fait rien du tout. ça aurait pu simplement être
foreach (char c in n + "")
ou
foreach (char c in (n++) + "")
de toute façon, rien ne change. la valeur d'origine est itérée
Concaténation de chaînes:
… string operator +(object x, string y);
L'opérateur binaire
+
effectue la concaténation de chaînes lorsqu'un ou les deux fichiers les opérandes sont de type chaîne. Si un opérande de concaténation de chaîne est null, une chaîne vide est substituée. Sinon, toute chaîne autre que la chaîne L’opérande est converti en sa représentation sous forme de chaîne en appelant le fichier méthode virtuelleToString
héritée du typeobject
. SiToString
renvoienull
, une chaîne vide est substituée . - ECMA-334 , page 201.
Donc, n.ToString()
est appelé. Tout le reste de la méthode ne fait que décomposer et recomposer le résultat sans aucun effet.
Cela aurait pu être écrit comme:
public string tostr(int n) => n.ToString();
Mais pourquoi?