Servy's got it right. Here are a few links and examples that might help further your understanding the topics of string concatenation and implicit conversion.
The C# language specification section 7.7.4 Addition operator states:
The binary + operator performs string concatenation when one or both
operands are of type string. [...] any non-string argument is
converted to its string representation by invoking the virtual
ToString method inherited from type object.
For your simple case of the int predefined type, you're seeing an invocation of int.ToString() according to the specification. But if you have a user-defined type, you might also run into implicit conversion to string (gory details in 6.4.3 User-defined implicit conversions).
To experiment, define a method that mimics MessageBox.Show(string). It's important not to call Console.WriteLine directly, since it provides numerous overloads of Write, including Write(Int32):
static void Write(string s)
{
Console.WriteLine(s);
}
And a few user-defined classes:
First, an empty class with no overrides or conversions.
class EmptyClass {
}
And a class that overrides Object.ToString.
class ToStringOnly {
public override string ToString() {
return "ToStringOnly";
}
}
Another class which demonstrates the implicit conversion to string:
class ImplicitConversion {
static public implicit operator string(ImplicitConversion b) {
return "Implicit";
}
}
And finally, I wonder what happens when a class both defines an implicit conversion and overrides Object.ToString:
class ImplicitConversionAndToString {
static public implicit operator string(ImplicitConversionAndToString b) {
return "Implicit";
}
public override string ToString() {
return "ToString";
}
}
A test for implicit conversions:
// Simple string, okay
Write("JustAString"); // JustAString
// Error: cannot convert from 'int' to 'string'
//Write(2);
// EmptyClass cannot be converted to string implicitly,
// so we have to call ToString ourselves. In this case
// EmptyClass does not override ToString, so the base class
// Object.ToString is invoked
//Write(new EmptyClass()); // Error
Write(new EmptyClass().ToString()); // StackOverflowCSharp.Program+EmptyClass
// implicit conversion of a user-defined class to string
Write(new ImplicitConversion()); // Implicit
// while ToStringOnly overrides ToString, it cannot be
// implicitly converted to string, so we have to again
// call ToString ourselves. This time, however, ToStringOnly
// does override ToString, and we get the user-defined text
// instead of the type information provided by Object.ToString
//Write(new ToStringOnly()); // ERROR
Write(new ToStringOnly().ToString()); // "ToStringOnly"
And, more relevant, a test for string concatenation:
// Simple string
Write("string"); // "string"
// binary operator with int on the right
Write("x " + 2); // "x 2"
// binary operator with int on the left
Write(3 + " x"); // "3 x"
// per the specification, calls Object.ToString
Write("4 " + new EmptyClass()); // "4 StackOverflowCSharp.Program+EmptyClass"
// the implicit conversion has higher precedence than Object.ToString
Write("5 " + new ImplicitConversion()); // "5 Implicit"
// when no implicit conversion is present, ToString is called, which
// in this case is overridden by ToStringOnly
Write("6 " + new ToStringOnly()); // "6 ToStringOnly"
And to seal it all up with a class that both defines an implicit conversion and overrides Object.ToString():
// In both cases, the implicit conversion is chosen
Write( new ImplicitConversionAndToString() ); // "Implicit"
Write( "8: " + new ImplicitConversionAndToString()); // 8: Implicit