这是一个很好的问题!
发生装箱的原因只有一个:当我们需要对值类型的引用时。您列出的所有内容都属于此规则。
例如,由于对象是引用类型,因此将值类型转换为对象需要对值类型的引用,这会导致装箱。
如果您希望列出每种可能的情况,还应该包括派生类,例如从返回对象或接口类型的方法返回值类型,因为这会自动将值类型转换为对象/接口。
顺便说一句,您敏锐地识别出的字符串连接情况也是源自于对象的转换。 + 运算符由编译器转换为对 string 的 Concat 方法的调用,该方法接受您传递的值类型的对象,因此会强制转换为对象,从而发生装箱。
多年来,我一直建议开发人员记住装箱的单一原因(我在上面指定),而不是记住每一个案例,因为列表很长而且很难记住。这也有助于理解编译器为 C# 代码生成的 IL 代码(例如,字符串上的 + 会产生对 String.Concat 的调用)。当您对编译器生成的内容有疑问并且发生装箱时,您可以使用 IL 反汇编器 (ILDASM.exe)。通常,您应该查找装箱操作码(只有一种情况可能会发生装箱,即使 IL 不包含装箱操作码,下面有更多详细信息)。
但我确实同意有些拳击事件不太明显。您列出了其中之一:调用值类型的非重写方法。事实上,由于另一个原因,这一点不太明显:当您检查 IL 代码时,您看不到装箱操作码,而是看到约束操作码,因此即使在 IL 中,装箱发生也不明显!我不会详细说明为什么要防止这个答案变得更长......
不太明显装箱的另一种情况是从结构调用基类方法时。例子:
struct MyValType
{
public override string ToString()
{
return base.ToString();
}
}
这里 ToString 被重写,因此在 MyValType 上调用 ToString 不会生成装箱。但是,该实现调用基本 ToString 并导致装箱(检查 IL!)。
顺便说一句,这两个不明显的拳击场景也源自上述单一规则。当在值类型的基类上调用方法时,必须有一些东西可以用于this关键字来参考。由于值类型的基类(始终)是引用类型,因此this关键字必须引用引用类型,因此我们需要对值类型的引用,因此由于单一规则而发生装箱。
以下是我的在线 .NET 课程中详细讨论拳击部分的直接链接:http://motti.me/mq http://motti.me/mq
如果您只对更高级的拳击场景感兴趣,这里有一个直接链接(尽管上面的链接一旦讨论了更基本的内容也将带您到那里):http://motti.me/mu http://motti.me/mu
我希望这有帮助!
Motti