C#中的装箱事件
Posted
技术标签:
【中文标题】C#中的装箱事件【英文标题】:Boxing Occurrence in C# 【发布时间】:2011-12-21 04:33:18 【问题描述】:我正在尝试收集 C# 中发生拳击的所有情况:
将值类型转换为System.Object
类型:
struct S
object box = new S();
将值类型转换为System.ValueType
类型:
struct S
System.ValueType box = new S();
将枚举类型的值转换为System.Enum
类型:
enum E A
System.Enum box = E.A;
将值类型转换为接口引用:
interface I
struct S : I
I box = new S();
在 C# 字符串连接中使用值类型:
char c = F();
string s1 = "char value will box" + c;
注意:char
类型的常量在编译时连接
注意:自 6.0 版以来 C# 编译器 optimizes concatenation 涉及 bool
、char
、IntPtr
、UIntPtr
类型
从值类型实例方法创建委托:
struct S public void M()
Action box = new S().M;
在值类型上调用非覆盖的虚方法:
enum E A
E.A.GetHashCode();
在is
表达式下使用C# 7.0 常量模式:
int x = …;
if (x is 42) … // boxes both 'x' and '42'!
C# 元组类型转换中的装箱:
(int, byte) _tuple;
public (object, object) M()
return _tuple; // 2x boxing
object
类型带值类型默认值的可选参数:
void M([Optional, DefaultParameterValue(42)] object o);
M(); // boxing at call-site
检查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
泛型类型进行类型测试:
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 进行优化
还有其他你知道的拳击情况吗,也许是隐藏的?
【问题讨论】:
我前段时间处理过这个问题,发现这很有趣:Detecting (un)boxing using FxCop 应该是社区wiki问题 可空类型呢?private int? nullableInteger
@allansson,可空类型只是一种值类型
请注意,从 .NET Core 2.1 开始,Enum.HasFlag
不会装箱,我相信:blogs.msdn.microsoft.com/dotnet/2018/04/18/…。虽然我可以在 2.1 应用程序中看到 IL 中的 box
指令,但它没有分配,因此我没有看到性能损失。
【参考方案1】:
这是一个很好的问题!
装箱的发生只有一个原因:当我们需要一个值类型的引用时。您列出的所有内容都属于此规则。
例如,由于 object 是引用类型,将值类型转换为 object 需要对值类型的引用,这会导致装箱。
如果您希望列出所有可能的场景,您还应该包括派生类,例如从返回对象或接口类型的方法中返回值类型,因为这会自动将值类型转换为对象/接口。
顺便说一句,您敏锐地识别出的字符串连接情况也源自强制转换为对象。 + 运算符由编译器转换为对字符串的 Concat 方法的调用,该方法接受您传递的值类型的对象,因此转换为对象并因此发生装箱。
多年来,我一直建议开发人员记住装箱的单一原因(我在上面指定),而不是记住每一个案例,因为列表很长而且很难记住。这也促进了对编译器为我们的 C# 代码生成的 IL 代码的理解(例如 + on string 产生对 String.Concat 的调用)。当您对编译器生成的内容有疑问并且发生装箱时,您可以使用 IL Disassembler (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/mu
我希望这会有所帮助!
莫蒂
【讨论】:
如果ToString()
被调用的特定值类型没有覆盖它,该值类型会在调用站点被装箱,还是将方法分派(不装箱)到自动生成的覆盖,除了链接(使用装箱)到基本方法之外什么都不做?
@supercat 调用任何在值类型中调用base
的方法都会导致装箱。这包括未被结构覆盖的虚拟方法和根本不是虚拟的Object
方法(如GetType()
)。见this question。
@ŞafakGür:我知道最终的结果将是拳击。我想知道它发生的确切机制。由于生成 IL 的编译器可能不知道该类型是值类型还是引用(它可能是通用的),所以无论如何它都会生成一个 callvirt。 JITter 会知道该类型是否是值类型,以及它是否覆盖 ToString,因此它可以生成调用站点代码来进行装箱;或者,它可以为每个不覆盖 ToString
的结构自动生成一个方法 public override void ToString() return base.ToString();
和...
...在该方法中进行装箱。由于该方法很短,因此可以内联。使用后一种方法做事将允许通过反射访问结构的 ToString()
方法,就像其他任何方法一样,并用于创建一个静态委托,该委托将结构类型作为 ref
参数[这样的事情适用于非继承struct methods],但我只是尝试创建一个这样的委托,但它没有用。是否可以为结构的ToString()
方法创建静态委托,如果可以,参数类型应该是什么?
链接已损坏。【参考方案2】:
在值类型上调用非虚拟 GetType() 方法:
struct S ;
S s = new S();
s.GetType();
【讨论】:
GetType
需要装箱,不仅因为它是非虚拟的,还因为值类型的存储位置,不像堆对象,没有 GetType()
可以用来识别的“隐藏”字段他们的类型。
@supercat 嗯。 1. 编译器添加的装箱和运行时使用的隐藏字段。可能是编译器添加了装箱,因为它知道运行时...... 2. 当我们在枚举值上调用非虚拟 ToString(string) 时,它也需要装箱,我不相信编译器会添加这个,因为它知道 Enum.ToString(string) 实现细节.因此,我认为,我可以说,当调用“基值类型”上的非虚拟方法时,总是会发生装箱。
我没有考虑过Enum
有任何自己的非虚拟方法,尽管Enum
的ToString()
方法需要访问类型信息。我想知道Object
、ValueType
或Enum
是否有任何非虚拟方法可以在没有类型信息的情况下执行其工作。【参考方案3】:
在 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)
返回类型
public object Bla()
return 1;
public IBla Bla() //IBla is an interface that 1 inherits
return 1;
检查不受约束的 T 与 null
public void Bla<T>(T obj)
if (obj == null) //boxes.
动态的使用
dynamic x = 42; (boxes)
另一个
enumValue.HasFlag
【讨论】:
【参考方案4】: 使用System.Collections
中的非泛型集合,例如
ArrayList
或 HashTable
。
当然,这些是您的第一个案例的具体实例,但它们可能是隐藏的陷阱。令人惊讶的是,我今天仍然遇到大量使用这些而不是 List<T>
和 Dictionary<TKey,TValue>
的代码。
【讨论】:
【参考方案5】:将任何值类型的值添加到 ArrayList 中会导致装箱:
ArrayList items = ...
numbers.Add(1); // boxing to object
【讨论】:
以上是关于C#中的装箱事件的主要内容,如果未能解决你的问题,请参考以下文章