我遇到了一种奇怪的方式来实现ToString()
,我想知道它是如何工作的:
public string tostr(int n)
{
string s = "";
foreach (char c in n-- + "") { //<------HOW IS THIS POSSIBLE ?
s = s + c;
}
return s;
}
迭代器是否假设char
的大小?
它隐式调用String.Concat(object, object)
方法,它连接两个指定对象的字符串表示:
string result = String.Concat("", n--);
String.Concat(object, object)
方法然后调用String.Concat(string, string)
。要阅读Concat
的源代码并进行深入检查,请先访问:String.cs source code in C# .NET然后在搜索TextBox
类型String
的页面中,然后单击结果中的String.cs
链接转到C#.NET中的String.cs源代码页面并检查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)
方法:
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
解释这个“一步一步”:
// 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
方法并将字符串连接在一起。
n--
的类型是int
,它通过使用string
将其与+
类型的""
连接而转换为string
。此外,string
实现IEnumerable<char>
,在其上进行与foreach
的实际迭代。
这段代码看起来很难理解,因为它是我认为语言中可怕的设计选择的结果。
+
运算符在string
中并不存在。如果你看一下参考源,或者MSDN page,string
唯一声明的运算符是==
和!=
。
真正发生的是编译器提取其魔术技巧之一,并将+
运算符转换为对静态方法string.Concat
的调用。
现在,如果您碰巧遇到foreach (char c in string.Concat(n--, ""))
,您可能会更好地理解代码,因为意图很明确:我想将两个对象连接为字符串,然后枚举构成结果字符串的char
s。
当你读到n-- + ""
时意图远非清晰,如果你碰巧有n-- + s
(s
是string
)则更糟糕。
两种情况下的编译器都决定您要将参数连接为字符串并自动将此调用映射到string.Concat(object, object)
。 C#的一个租户是,除非编码器的意图明确,否则挥动红旗并要求编码人员澄清他的意图。在这种特殊情况下,该租户完全受到侵犯。
恕我直言,任何不是string + string
的东西都应该是一个编译时错误,但那列火车很多年前就已经过了。
分解代码以说明它发生的原因......
foreach(char c in n-- + "")
使用带有字符串的+
运算符作为操作数之一将结果转换为字符串,而不管其他原始操作数是什么,只要它具有+的实现。在这样做时,n参与了string.Concat
方法,您可以从以下Autos调试窗口的屏幕截图中看到...
我可以推断出用“34”调用方法。其中的循环标识符定义为char c
并通过字符串。这是因为string
实现了IEnumerable<char>
,你可以从字符串类型的“Go to definition”结果的屏幕截图中看到:
所以从那一点开始,它就像你在迭代任何其他列表/数组一样......或者更准确地说,IEnumerable与foreach
并获得每个char
。 n
在平均时间已经改为n-- // 33 in my case
。在这一点上,n
的值是无关紧要的,因为你正在遍历表达式n-- + ""
的结果。它可能也是n++
,你会得到相同的结果。
线s = s + c;
应该是非常自我解释的,以防它不是,你从n-- + ""
的临时字符串结果拉出的每个字符被附加到你的空(开头)string s = "";
。它产生一个字符串,因为如前所述,+
与一个字符串相关将导致一个字符串。完成所有字符后,它将返回字符串表示形式。
我已经使用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 Article: Increment (++) and Decrement (--) Operators之后,我才清楚这一点
递增和递减运算符用作修改存储在变量中的值并访问该值的快捷方式。可以在前缀或后缀语法中使用任一运算符。
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
减1之前由n
读取并使用。
即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;
}
字符串连接:
… string operator +(object x, string y);
当一个或两个操作数是字符串类型时,二进制
+
运算符执行字符串连接。如果字符串连接的操作数为null,则替换空字符串。否则,通过调用从类型ToString
继承的虚拟object
方法,将任何非字符串操作数转换为其字符串表示形式。如果ToString
返回null
,则替换空字符串。 - ECMA-334,第201页。
所以,n.ToString()
被称为。该方法中的其他所有内容只是分解并重新组合结果无效。
它原本可以写成:
public string tostr(int n) => n.ToString();
但为什么?
n-- + ""
是相同的
n + ""
因为我们以后不使用n的值
n + ""
是相同的
n.ToString()
因为+运算符使用int和string将int转换为string,所以
foreach (char c in n-- + "")
是相同的
foreach (char c in n.ToString())
其余的很简单。
由于n--
是后递减,因此n的值仅在连接后改变。所以基本上它什么都不做。它可能只是
foreach (char c in n + "")
要么
foreach (char c in (n++) + "")
两种方式都没有变化。原始值被迭代