Reputation: 392
I came across a strange way to implement ToString()
and I am wondering how it works:
public string tostr(int n)
{
string s = "";
foreach (char c in n-- + "") { //<------HOW IS THIS POSSIBLE ?
s = s + c;
}
return s;
}
Is the iterator assuming the size of a char
?
Upvotes: 25
Views: 3500
Reputation: 2505
I've used ReSharper to convert the function to Linq statements which may help some people understand what's going (or just confuse people even more).
public string tostrLinq(int n)
{
return string.Concat(n--, "").Aggregate("", (string current, char c) => current + c);
}
As others have already stated, basically the inputted int
is concatenated with an empty string
which basically gives you a string representation of the the int
. As string
implements IEnumerable
, the foreach
loop breaks up the string into a char[]
, giving each character of the string on each iteration. The loop body just then joins the characters back together as a string through concatenating each char
.
So for example, given input of 5423
, it gets converted to "5423"
, and then broken up into "5"
, "4"
, "2"
, "3"
and finally stitched back to "5423"
.
Now the part that really hurt my head for a while was the n--
. If this decrements the int
, then why don't we get "5422"
returned instead? This wasn't clear to me until after I read the MSDN Article: Increment (++) and Decrement (--) Operators
The increment and decrement operators are used as a shortcut to modify the value stored in a variable and access that value. Either operator may be used in a prefix or postfix syntax.
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
So because the decrement operator is applied at the end to n
, the value of n
is read and used by string.Concat
before n
is decremented by 1.
I.e. string.Concat(n--,"")
is going to give the same output as string.Contact(n, ""); n = n - 1;
.
So to get "5422"
we would change it to string.Concat(--n, "")
so that n
is decrement first before it is passed to string.Contact
.
TL;DR; The function is a round about way of doing n.ToString()
Interestingly, I also used ReSharper to convert it to a for
loop but the function no longer works as n
is decremented on each iteration of the for loop, unlike the foreach
loop:
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;
}
Upvotes: 0
Reputation: 32780
This code looks incomprehensible because its the outcome of what I consider an awful design choice in the language.
The +
operator doesn't really exist in string
. If you look at the reference source, or the MSDN page, the only declared operators for string
are ==
and !=
.
What really happens is that the compiler pulls one of its magic tricks, and converts the +
operator into a call to the static method string.Concat
.
Now if you happened to encounter foreach (char c in string.Concat(n--, ""))
you'd probably understand the code much better, because the intent is clear: I want to concatenate two objects as strings and then enumerate the char
s that make up the resulting string.
When you read n-- + ""
that intent is far from clear, and its worse if you happen to have n-- + s
(s
being a string
).
The compiler in both cases decides that you want to concatenate the arguments as strings and automatically maps this call to string.Concat(object, object)
. One of C#'s tenants is that, unless the intent of the coder is clear, wave a red flag and ask the coder to clarify his intent. In this particular case, that tenant is violated completely.
IMHO, anything that isn't a string + string
should have been a compile time error, but that train passed along many many years ago.
Upvotes: 6
Reputation: 662
Since n--
is a post decrement the value of n is only changed after the concatenation. so essentially it is doing nothing at all. it could have simply been
foreach (char c in n + "")
or
foreach (char c in (n++) + "")
either way nothing changes. the original value gets iterated
Upvotes: 0
Reputation: 39976
It calls the String.Concat(object, object)
method implicitly, which concatenates the string representations of two specified objects:
string result = String.Concat("", n--);
The String.Concat(object, object)
method then calls String.Concat(string, string)
. To read the Concat
's source and check it in depth, first go here: String.cs source code in C# .NET and then in that page in the search TextBox
type String
and then click on the String.cs
link in the results to go to the String.cs source code in C# .NET page and check the Concat
method.
This is the method definition:
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());
}
As you see this calls public static String Concat(String str0, String str1)
method finally:
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;
}
And this is the underlying IL, by 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
Upvotes: 19
Reputation: 2423
To break down your code to show why it happens...
foreach(char c in n-- + "")
using the +
operator with a string as one of the operands will convert the result to a string regardless of what the other primitive operand was, as long as it had an implementation of +. In doing so, n participates in the string.Concat
method as you can see from the following screenshot of the Autos debug window...
I called the method with "34" as you can infer. The loop identifier in there is defined as char c
and is going through a string. This works because string
implements IEnumerable<char>
as you can see from the screenshot of the result of "Go to definition" of the string type:
So from that point, it works the same as if you were iterating through any other list/array... or more accurately, IEnumerable with foreach
and gets each individual char
. n
in the mean time has been changed to n-- // 33 in my case
. At this point, the value of n
is inconsequential since you are iterating through the result of the expression n-- + ""
. It may as well have been n++
and you would get the same result.
The line s = s + c;
should be pretty self explanatory and just in case it isn't, each char you pull from the temporary string result of n-- + ""
is appended to your empty (in the beginning) string s = "";
. It results in a string because as mentioned before +
with a string involved will result in a string. Once you are done going through all the chars, it returns the string representation.
Upvotes: 1
Reputation: 20812
String concatenation:
… string operator +(object x, string y);
The binary
+
operator performs string concatenation when one or both operands are of type string. If an operand of string concatenation is null, an empty string is substituted. Otherwise, any non-string operand is converted to its string representation by invoking the virtualToString
method inherited from typeobject
. IfToString
returnsnull
, an empty string is substituted. — ECMA-334, page 201.
So, n.ToString()
is called. Everything else in the method is just decomposing and recomposing the result to no effect.
It could have been written as:
public string tostr(int n) => n.ToString();
But, why?
Upvotes: 0
Reputation: 17605
The type of n--
is int
, which gets converted to string
by using +
to concatenate it with ""
, which is of type string
. Furthermore, string
implements IEnumerable<char>
, over which the actual iteration with foreach
takes place.
Upvotes: 10
Reputation: 5940
Explaining this "step by step" :
// 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
}
So basically if you concatenate string
with int
it will automatically call int.ToString
method and join the string together.
Upvotes: 15