Как int + string становится строкой?
Я натолкнулся на странный способ реализовать ToString()
, и мне интересно, как это работает:
public string tostr(int n)
{
string s = "";
foreach (char c in n-- + "") { //<------HOW IS THIS POSSIBLE ?
s = s + c;
}
return s;
}
Является ли итератором предполагаемый размер a char
?
Ответы
Ответ 1
Он вызывает неявный метод String.Concat(object, object)
, который объединяет строковые представления двух указанных объектов:
string result = String.Concat("", n--);
Затем метод String.Concat(object, object)
вызывает String.Concat(string, string)
. Чтобы прочитать источник Concat
и проверить его по глубине, сначала перейдите сюда: Исходный код String.cs в С#.NET, а затем на этой странице в теге TextBox
введите String
, а затем нажмите ссылку String.cs
в результатах, чтобы перейти к исходному коду String.cs на странице С#.NET и проверить метод Concat
.
Это определение метода:
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());
}
Как вы видите, это вызывает метод public static String Concat(String str0, String str1)
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;
}
И это базовый IL, 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
Ответ 2
Объяснение этого "шаг за шагом":
// 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
}
В принципе, если вы соедините string
с int
, он автоматически вызовет метод int.ToString
и соедините цепочку вместе.
Ответ 3
Тип n--
равен int
, который преобразуется в string
, используя +
, чтобы объединить его с ""
, который имеет тип string
. Кроме того, string
реализует IEnumerable<char>
, по которому имеет место фактическая итерация с foreach
.
Ответ 4
Этот код выглядит непонятным, потому что он является результатом того, что я считаю ужасным выбором дизайна на языке.
Оператор +
действительно не существует в string
. Если вы посмотрите на источник ссылки или страницу MSDN, единственными объявленными операторами для string
являются ==
и !=
.
Что действительно происходит, так это то, что компилятор извлекает один из своих волшебных трюков и преобразует оператор +
в вызов статического метода string.Concat
.
Теперь, если вы столкнулись с foreach (char c in string.Concat(n--, ""))
, вы, вероятно, поняли бы код намного лучше, потому что цель понятна: я хочу объединить два объекта в виде строк, а затем перечислить char
, которые составляют итоговую строку.
Когда вы читаете n-- + ""
, это намерение далека от ясности и хуже, если у вас n-- + s
(s
есть string
).
Компилятор в обоих случаях решает, что вы хотите объединить аргументы в виде строк и автоматически сопоставить этот вызов с string.Concat(object, object)
. Один из арендаторов С# заключается в том, что, если цель кодера не ясна, нарисуйте красный флаг и попросите кодера прояснить его намерение. В этом конкретном случае этот арендатор полностью нарушается.
ИМХО, все, что не является string + string
, должно было быть ошибкой времени компиляции, но этот поезд прошел много лет назад.
Ответ 5
Чтобы разбить свой код, чтобы показать, почему это происходит...
foreach(char c in n-- + "")
с помощью оператора +
со строкой, так как один из операндов преобразует результат в строку независимо от того, какой был другой примитивный операнд, если у него была реализация+. При этом n участвует в методе string.Concat
, как вы можете видеть из следующего снимка экрана отладки Autos...
![Окно авто, показывающее значения идентификатора]()
Я назвал метод с "34", как вы можете сделать вывод. Идентификатор цикла там определен как char c
и проходит через строку. Это работает, потому что string
реализует IEnumerable<char>
, как вы можете видеть на скриншоте результата "Перейти к определению" типа строки:
![Результат правого щелчка типа строки и перехода к определению]()
Итак, с этой точки он работает так же, как если бы вы выполняли итерацию через любой другой список/массив... или точнее, IEnumerable с foreach
и получает каждый отдельный char
. n
в среднем было изменено на n-- // 33 in my case
. В этот момент значение n
несущественно, поскольку вы выполняете итерацию через результат выражения n-- + ""
. Это может быть и n++
, и вы получите тот же результат.
Строка s = s + c;
должна быть довольно понятной и на всякий случай, если это не так, каждый char, который вы извлекаете из временного результата строки n-- + ""
, добавляется к вашему пусту (в начале) string s = "";
, Это приводит к строке, поскольку, как упоминалось ранее, +
с вовлеченной строкой приведет к строке. После того, как вы пройдете все символы, он вернет строковое представление.
Ответ 6
Я использовал ReSharper для преобразования функции в выражения Linq, которые могут помочь некоторым людям понять, что происходит (или просто путать людей еще больше).
public string tostrLinq(int n)
{
return string.Concat(n--, "").Aggregate("", (string current, char c) => current + c);
}
Как уже говорили другие, в основном введенный int
конкретизируется с пустым string
, который в основном дает вам строковое представление int
. Поскольку string
реализует IEnumberable
, цикл foreach
разбивает строку на char[]
, давая каждому символу строки на каждой итерации. Тело цикла просто присоединяет символы обратно вместе в виде строки, объединяя каждый char
.
Так, например, при вводе 5423
он преобразуется в "5423"
, а затем разбивается на "5"
, "4"
, "2"
, "3"
и, наконец, сшивается обратно на "5423"
.
Теперь часть, которая действительно повредила мне голову, была n--
. Если это уменьшает значение int
, то почему бы нам не вернуть "5422"
? Это мне было непонятно до тех пор, пока я не прочитал Операторы MSDN: Increment (++) и Decrement (-)
Операторы приращения и уменьшения используются как ярлык для изменения значение, хранящееся в переменной, и доступ к этому значению. Любой оператор может использоваться в синтаксисе префикса или постфикса.
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
Так как оператор декремента применяется в конце до n
, значение n
считывается и используется string.Concat
до n
уменьшается на 1.
т.е. string.Concat(n--,"")
будет давать тот же результат, что и string.Contact(n, ""); n = n - 1;
.
Итак, чтобы получить "5422"
, мы изменим его на string.Concat(--n, "")
, чтобы n
уменьшался сначала до того, как он будет передан в string.Contact
.
TL; DR; Функция представляет собой круглый способ выполнения n.ToString()
Интересно, что я также использовал ReSharper для преобразования его в цикл for
, но функция больше не работает, поскольку n
уменьшается на каждой итерации цикла for, в отличие от цикла 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;
}
Ответ 7
Конкатенация строк:
…
string operator +(object x, string y);
Двоичный оператор +
выполняет конкатенацию строк, когда один или оба операнды имеют тип string. Если операнд конкатенации строк null, заменяется пустая строка. В противном случае любая строка операнд преобразуется в его строковое представление, вызывая virtual ToString
, унаследованный от типа object
. Если ToString
возвращает null
, заменяется пустая строка. - ECMA-334, стр. 201.
Итак, вызывается n.ToString()
. Все остальное в методе просто разлагает и рекомпозирует результат без эффекта.
Это могло быть написано как:
public string tostr(int n) => n.ToString();
Но почему?
Ответ 8
n-- + ""
совпадает с
n + ""
потому что мы не используем значение n позже
n + ""
совпадает с
n.ToString()
потому что оператор +, используемый с int и string, преобразует int в строку, поэтому
foreach (char c in n-- + "")
совпадает с
foreach (char c in n.ToString())
остальное просто.
Ответ 9
Так как n--
является пост-декрементом, значение n изменяется только после конкатенации. так что по существу он ничего не делает. это могло бы быть просто
foreach (char c in n + "")
или
foreach (char c in (n++) + "")
в любом случае ничего не меняется. исходное значение получает итерацию