拳击发生在C#

我试图收集在C#中发生拳击的所有情况:

  • 将值types转换为System.Objecttypes:

     struct S { } object box = new S(); 
  • 将值types转换为System.ValueTypetypes:

     struct S { } System.ValueType box = new S(); 
  • 将枚举types的值转换为System.Enumtypes:

     enum E { A } System.Enum box = EA; 
  • 将值types转换为接口引用:

     interface I { } struct S : I { } I box = new S(); 
  • 在C#string连接中使用值types:

     char c = F(); string s1 = "char value will box" + c; 

    注意: chartypes的常量在编译时连接在一起

    注意:自C#版本6.0以来,编译器优化了涉及boolcharIntPtrUIntPtrtypes的连接

  • 从值types实例方法创build委托:

     struct S { public void M() {} } Action box = new S().M; 
  • 在值types上调用未覆盖的虚拟方法:

     enum E { A } EAGetHashCode(); 
  • 在expression式下使用C#7.0常量模式:

     int x = …; if (x is 42) { … } // boxes both 'x' and '42'! 
  • 在C#元组types转换的拳击:

     (int, byte) _tuple; public (object, object) M() { return _tuple; // 2x boxing } 
  • 具有值types默认值的objecttypes的可选参数:

     void M([Optional, DefaultParameterValue(42)] object o); M(); // boxing at call-site 
  • 检查null的无约束genericstypes的null

     bool M<T>(T t) => t != null; string M<T>(T t) => t?.ToString(); // ?. checks for null M(42); 

    注意:这可能会在某些.NET运行时通过JIT进行优化

  • 使用is / as操作符键入无约束或structgenericstypes的testing值:

     bool M<T>(T t) => t is int; int? M<T>(T t) => t as int?; IEquatable<T> M<T>(T t) => t as IEquatable<T>; M(42); 

    注意:这可能会在某些.NET运行时通过JIT进行优化

还有更多的拳击情况,也许是隐藏的,你知道吗?

这是一个很好的问题!

拳击发生的原因正是一个:当我们需要一个值types的引用 。 你列出的所有东西都属于这个规则。

例如,因为object是一个引用types,所以将一个值types转换为对象需要引用一个值types,这会导致装箱。

如果你希望列出每种可能的情况,你还应该包括衍生物,例如从返回对象或接口types的方法中返回一个值types,因为这会自动将值types转换为对象/接口。

顺便说一句,你敏锐地标识的string连接的情况也从铸造到对象派生。 编译器将+运算符转换为对stringConcat方法的调用,该方法接受传递的值types的对象,因此将转换为对象并进行装箱。

多年来,我一直build议开发人员记住拳击的单一原因(我在上面指出),而不是记住每一个案例,因为名单很长,很难记住。 这也提高了对编译器为我们的C#代码生成的IL代码的理解(例如,对string的+会产生对String.Concat的调用)。 当你怀疑编译器产生了什么,如果出现拳击,你可以使用IL反汇编程序(ILDASM.exe)。 通常情况下,您应该查找框操作码(即使IL不包含框操作码,下面还有更多详细信息,只有一种情况可能发生装箱)。

但我同意一些拳击事件不那么明显。 您列出其中的一个:调用值types的未覆盖的方法。 事实上,这是不明显的另一个原因:当你检查IL代码,你没有看到框操作码,但约束操作码,所以即使在IL这是不明显的拳击发生! 我不会详细解释为什么要阻止这个答案变得更长。

不太明显的装箱的另一个情况是从一个结构体调用一个基类方法。 例:

 struct MyValType { public override string ToString() { return base.ToString(); } } 

这里ToString被覆盖,因此在MyValType上调用ToString将不会生成装箱。 但是,实现调用基础ToString并导致装箱(检查IL!)。

顺便说一句,这两个非明显的拳击情况也来自上面的单一规则。 当在一个值types的基类上调用一个方法时,必须有一些关键字可以引用。 由于值types的基类是(总是)引用types,所以this关键字必须引用一个引用types,所以我们需要一个值types的引用,所以由于单个规则而发生装箱。

这里是直接链接到我的在线.NET课程的部分,详细讨论拳击: http : //motti.me/mq

如果你只对更高级的拳击场景感兴趣,那么这里有一个直接的链接(尽pipe上面的链接会带你到那里,一旦它讨论更基本的东西): http : //motti.me/mu

我希望这有帮助!

Motti

在值types上调用非虚拟的GetType()方法:

 struct S { }; S s = new S(); s.GetType(); 

在Motti的回答中提到,只是用代码示例说明:

涉及的参数

 public void Bla(object obj) { } Bla(valueType) public void Bla(IBla i) //where IBla is interface { } Bla(valueType) 

但是这是安全的:

 public void Bla<T>(T obj) where T : IBla { } Bla(valueType) 

返回types

 public object Bla() { return 1; } public IBla Bla() //IBla is an interface that 1 inherits { return 1; } 

针对null检查不受约束的T

 public void Bla<T>(T obj) { if (obj == null) //boxes. } 

使用dynamic

 dynamic x = 42; (boxes) 

另一个

enumValue.HasFlag

  • System.Collections使用非generics集合,如ArrayListHashTable

当然这些是你的第一种情况的特定情况,但是它们可能是隐藏的陷阱。 令人惊讶的是我今天仍然使用这些代码,而不是List<T>Dictionary<TKey,TValue>

将任何值types值添加到ArrayList会导致装箱:

 ArrayList items = ... numbers.Add(1); // boxing to object