There are two important things going on here:
First, The expression "string" + 1 is evaluated at run-time while "string" + "1" is evaluated at compile-time.
Secondly, you're using reference comparison. The run-time generated strings actually reference different object while the compile-time generated strings reference the same object, so the first expression is false and the second expression is true.
If you're interested, the generated IL is:
// bool a1 = (object)("string" + 1) == ("string" + 1);
// bool a2 = (object)("string" + "1") == ("string" + "1");
IL_0000: ldstr "string"
IL_0005: ldc.i4.1
IL_0006: box System.Int32
IL_000B: call System.String.Concat
IL_0010: ldstr "string"
IL_0015: ldc.i4.1
IL_0016: box System.Int32
IL_001B: call System.String.Concat
IL_0020: ceq
IL_0022: stloc.0 // a1
IL_0023: ldstr "string1"
IL_0028: ldstr "string1"
IL_002D: ceq
IL_002F: stloc.1 // a2
==is fine for comparing strings, as long as they're both treated as strings - this is just a funky case when you're dealing with strings that are cast as objects. But"string" + 1 == "string1"still returns true. It's Java that you really need to useequals. The biggest reason I avoidEqualsin .NET is that you have to check the first one for null every time, to avoid NullReferenceException, where using==works fine for comparing nulls.