为什么int的插值表达式会出现装箱?

Why does boxing occur in the interpolation expression of int?

例如下面的代码:

int n=1;
string str=$"{n}";

但是显式添加ToString()后,装箱就不会发生了。

int n=1;
//The compiler will recommend removing the explicit call of the ToString() method
string str=$"{n.ToString()}";

CLR via C#写到String.Format会在内部调用ToString方法来获取对象的字符串表示。

既然内部调用了ToString方法,为什么会出现示例1中的装箱?

“调用ToString”并不是防止拳击的妙法。如果您在 ToString 它被装箱后调用 ToString ,装箱仍然会发生,字符串插值就是这种情况。

如您所知,字符串插值通常会对 string.Format 调用脱糖。如果您查看 list of overloads available,您会发现没有采用像 intlong 这样的值类型的重载。每个重载都需要 object。要将 int 传递给这些方法,首先需要对其进行装箱。 string.Format 然后在某个时候 在盒装 object.

上调用 ToString

将此与在字符串插值中直接调用 ToString 进行比较。没有到引用类型的转换 (object),所以没有装箱。

我注意到,尽管您的代码使用的是 C# 内插字符串,但它并未使用 FormattedString class,因为 C# 编译器只会在以下情况下使用 FormattedString内插字符串被直接分配给 FormattedString 类型的变量、字段或参数(我不同意,但无论如何)。

The book CLR via C# writes that String.Format will call the ToString method internally to get the string representation of the object.

是的,但是所有 String.Format 重载都使用 Object 类型的参数或 params Object[],这必然意味着装箱其参数。

why does the boxing occur in Example 1?

因为它必须将int n传递给Object arg0

这是我在 LinqPad(C# 8.0,启用了编译器优化)中编译您的第一个代码块时生成的 IL:

IL_0000:  ldc.i4.1    
IL_0001:  stloc.0     
IL_0002:  ldstr       "{0}"
IL_0007:  ldloc.0     
IL_0008:  box         System.Int32
IL_000D:  call        System.String.Format
IL_0012:  pop         
IL_0013:  ret   

您可以在指令偏移 IL_0008 处看到 box 指令,就在它传递到 String.Format 之前。

为了格式化字符串,它只能采用 Object 类型,它调用 ToString() 以将其连接起来。装箱基本上是将值类型转换为引用类型,这是在隐式转换为对象时发生的事情,因此它可以存储在堆中。