Nabil Lamriben
Nabil Lamriben

Reputation: 392

How int + string becomes string?

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

Answers (8)

Adrian Sanguineti
Adrian Sanguineti

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

InBetween
InBetween

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 chars 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

Robert Juneau
Robert Juneau

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

Salah Akbari
Salah Akbari

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

Fabulous
Fabulous

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...

Autos window showing identifier values

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:

Result of right clicking string type and going to definition

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

Tom Blodget
Tom Blodget

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 virtual ToString method inherited from type object. If ToString returns null, 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

Codor
Codor

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

mrogal.ski
mrogal.ski

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

Related Questions