拳击发生在C#
我试图收集在C#中发生拳击的所有情况:
-
将值types转换为
System.Object
types:struct S { } object box = new S();
-
将值types转换为
System.ValueType
types:struct S { } System.ValueType box = new S();
-
将枚举types的值转换为
System.Enum
types: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;
注意:
char
types的常量在编译时连接在一起注意:自C#版本6.0以来,编译器优化了涉及
bool
,char
,IntPtr
,UIntPtr
types的连接 -
从值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默认值的
object
types的可选参数: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
操作符键入无约束或struct
genericstypes的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集合,如ArrayList
或HashTable
。
当然这些是你的第一种情况的特定情况,但是它们可能是隐藏的陷阱。 令人惊讶的是我今天仍然使用这些代码,而不是List<T>
和Dictionary<TKey,TValue>
。
将任何值types值添加到ArrayList会导致装箱:
ArrayList items = ... numbers.Add(1); // boxing to object