简而言之,我认为拳击是一种烦恼。 让我们看看一些替代方案...
public class Box<T>
where T : struct
{
public T Value { get; set; }
public static implicit operator T(Box<T> box)
{
return box.Value;
}
}
System.Int32 派生自抽象类 System.ValueType,而抽象类 System.ValueType 又派生自类 System.Object。 您无法从 C# 中的 System.ValueType 派生,但我猜测 struct 关键字正是这样做的,并且 CLI 将此类类型定义识别为具有按值传递语义。 无论如何,当将结构分配给对象类型时,就会发生装箱。 我不想陷入拳击本身,而是想直接开始。
我查看了 C# 编译器生成的一些 IL。
object obj = 1;
.locals init ([0] object obj)
L_0000: nop
L_0001: ldc.i4.1
L_0002: box int32 // Convert a value type (of the type specified in valTypeToken) to a true object reference.
L_0007: stloc.0
在 MSDN 上发现了这一点...
值类型在公共语言基础结构 (CLI) 中有两种单独的表示形式:
这让我得出结论,编写这样的代码应该同样昂贵......
var box = obj as Box<int>;
if (box != null)
{
Console.WriteLine(box.Value);
}
如果我打算将相同的值作为 System.Object 传递,我真的想每次都拆箱并装箱 ValueType 吗? 我的直觉告诉我不,但我真的找不到任何人愿意评论所有这些胡言乱语的好动机?
编辑
有人发现自己这样做过吗? 我意识到这可能看起来很奇怪,但在某一时刻我发现自己处于一种位置,我想根据几种不同的表示来抽象计算。 我是这样用 lambda 表达式做到的。 它与装箱并不真正相关,但它允许我将任何 ValueType(该结构方便地 8 字节对齐)视为一种单一类型“ReinterpretCast”。
[StructLayout(LayoutKind.Explicit)]
public struct ReinterpretCast
{
[FieldOffset(0)] sbyte @sbyte;
[FieldOffset(0)] byte @byte;
[FieldOffset(0)] short @ushort;
[FieldOffset(0)] ushort @short;
[FieldOffset(0)] int @int;
[FieldOffset(0)] uint @uint;
[FieldOffset(0)] long @long;
[FieldOffset(0)] ulong @ulong;
[FieldOffset(0)] float @float;
[FieldOffset(0)] double @double;
}
In short, I think boxing is an annoyance. Let's look at some alternatives...
public class Box<T>
where T : struct
{
public T Value { get; set; }
public static implicit operator T(Box<T> box)
{
return box.Value;
}
}
System.Int32 derives from abstract class System.ValueType which derives from class System.Object. You cannot derive from System.ValueType in C# but I would guess that the struct keyword does exactly that and the CLI recognizes these kind of type definitions as having pass-by-value semantic. Anyhow, when a struct is assigned to type of object boxing occurs. I don't wanna get caught up in boxing per se, instead I wanna get straight to it.
I looked at some of the IL generated by the C# compiler.
object obj = 1;
.locals init ([0] object obj)
L_0000: nop
L_0001: ldc.i4.1
L_0002: box int32 // Convert a value type (of the type specified in valTypeToken) to a true object reference.
L_0007: stloc.0
Found this on MSDN...
A value type has two separate representations within the Common Language Infrastructure (CLI):
-
A 'raw' form used when a value type is embedded within another object or on the stack.
-
A 'boxed' form, where the data in the value type is wrapped (boxed) into an object so it can exist as an independent entity.
This have lead me to conclude that it should be equally expensive to write code like this...
var box = obj as Box<int>;
if (box != null)
{
Console.WriteLine(box.Value);
}
If I intend to pass that same value around as an System.Object do I really wanna unbox and box the ValueType every time? My gut feeling is telling me no, but I cant really find good motivation anyone care to comment on all this blabbering?
EDIT
Anyone ever find themselves doing this? I realize that it might look bizarre but at one point I found myself in a position were I wanted to abstract computations based of several different representations. I did it like this and with lambda expressions. Its not really related to boxing but it sort of allowed me to treat any ValueType (this struct is conveniently 8-byte aligned) as if it were one single type "ReinterpretCast".
[StructLayout(LayoutKind.Explicit)]
public struct ReinterpretCast
{
[FieldOffset(0)] sbyte @sbyte;
[FieldOffset(0)] byte @byte;
[FieldOffset(0)] short @ushort;
[FieldOffset(0)] ushort @short;
[FieldOffset(0)] int @int;
[FieldOffset(0)] uint @uint;
[FieldOffset(0)] long @long;
[FieldOffset(0)] ulong @ulong;
[FieldOffset(0)] float @float;
[FieldOffset(0)] double @double;
}
发布评论
评论(5)
我不完全确定你的问题。 您只是想问您的解决方案是否比普通拳击更好? 它确实有一定的吸引力。 如果您想问为什么拳击最初没有以这种方式实现,请记住 .NET 一开始就没有泛型。
编辑:无论如何,拳击对于仿制药来说相对较少。 不要忘记,如果对您的类型实例的引用作为
object
传递(对于装箱来说通常就是这种情况),您仍然需要执行运行时转换。 另外不要忘记接口 - 如果值类型实现接口,那么用于装箱的相应引用类型也会实现接口。 您的解决方案不会删除装箱的使用,因为您无法让您的类型“假装”实现该接口。 (您也许可以使用 DLR 做一些事情,但到那时大部分要点已经丢失了:)I'm not entirely sure of your question here. Are you just asking whether your solution is perhaps better than normal boxing? It certainly has some appeal. If you're asking why boxing wasn't implemented this way in the first place, just remember that .NET didn't have generics to start with.
EDIT: Boxing is relatively rare with generics anyway. Don't forget that you'll still have to do a runtime cast if a reference to an instance of your type is passed around as
object
(which is usually the case anyway for boxing). Also don't forget interfaces - if a value type implements an interface, so does its corresponding reference type used for boxing. Your solution won't remove that use of boxing, as you can't make your type "pretend" to implement the interface. (You might be able to do something with the DLR, but by that time most of the point has been lost :)我们认为更快的速度完全无关紧要。 在考虑什么更快时,只有分析器才是相关的。
What we think is faster is completely irrelevant. Only the profiler is relevant when considering what is faster.
简短的回答:不,你不会想要进行大量的装箱/拆箱。它会产生开销:额外的垃圾,并且往往很慢(尽管我认为速度已经在稍后进行了优化) 但是
编辑:但是,如果您“将相同的值作为对象传递”,则在需要之前不将其返回到值类型,那么它会一直保持装箱状态而不会被拆箱,
,正如大家所说,无论如何,您不需要“将相同的值作为对象传递”,除非您正在使用 Framework 1.x,当时 BCL 集合类使用 System.Object 和任何进入的值类型都会被装箱
(顺便说一句,如果通过接口访问,则装箱的值类型不会被取消装箱。)
The short answer: No, you wouldn't want to do a lot of boxing/unboxing. It creates overhead: extra extra garbage and tends to be slow (although I think the speed has been optimized in later framework versions).
EDIT: However, if you "pass that same value around as an Object", without casting is back to the value type until it's needed, then it stays boxed the whole way without being unboxed.
But, as everyone said, you don't need to "pass that same value around as an Object" anyway. That's what generics are for, unless you are working on Framework 1.x. Boxing was more relevant back then when the BCL collection classes used System.Object and any value type that went in was boxed.
(As an aside, boxed value types are NOT unboxed if accessed through an interface.)
您的问题的标题错过了我认为最有趣的方面:系统的装箱行为与
Box
类型的装箱行为有何不同。 有一些区别:(1) 盒装
T
将使用相同的代码实现与T
相同的接口,但主要使用类语义而不是值来表现语义,但有一个奇怪的Equals
方法。(2) 在 C# 或 vb.net 中,改变盒装
T
通常会很麻烦,但盒装T
永远不会真正是不可变的,因为允许不受信任的可验证代码执行此操作,即使这在某些语言中很尴尬(在 C++/CLI 中很容易)。 即使像装箱Int32
这样的类型在装箱时也是可变的。 相比之下,我们可以定义一个ImmutableBox
,它采用T
类型的构造函数,其字段是真正不可变的。(3) 即使是装箱时很容易可变的结构类型(例如,因为它们实现了像
IEnumerator
这样的可变接口),因此表现得像可变引用类型,也无法实现Equals
表示引用相等(这将是可变引用类型的正常行为),但通常用它来测试其瞬时状态的相等。 相比之下,如果存在可变和不可变框类型,则不可变类型可以检查状态相等性,而可变类型可以检查引用相等性。(4) 从
T
到Box
的隐式转换不会排除类型T
定义到接口类型的隐式转换的可能性。 相比之下,因为所有类型都可以隐式转换为Object
,所以 vb.net 和 C# 都不允许在结构类型和接口之间进行隐式用户转换。(5) 如果没有专门的编译器对装箱的支持,目前接受
Object[]
参数数组的方法将无法自动转换来自Int32
等类型的参数到Box
。 另一方面,添加一种请求自动装箱某些参数的方法可能比到处都隐式装箱更好。 请注意,如果存在这样的方法,它可以指定每个参数都应放置在Box
中,从而可以区分传递T
和Box
(因为后者将作为Box>
传递。The title of your question misses what I think is the most interesting aspect: in what way is the system's boxing behavior different from that of a
Box<T>
type. There are a few differences:(1) A boxed
T
will implement the same interfaces as aT
, using the same code, but will mostly behave using class semantics rather than value semantics, but with a quirkyEquals
method.(2) Mutating a boxed
T
will generally be nuisance in C# or vb.net, but a boxedT
will never really be immutable since untrusted verifiable code is allowed to do it, even if it's awkward in some languages (it's easy in C++/CLI). Even types like boxedInt32
are mutable when boxed. By contrast, one could define anImmutableBox<T>
, which took a constructor of typeT
, whose fields would be truly immutable.(3) Even structure types which are easily mutable when boxed (e.g. because they implement a mutating interface like
IEnumerator<T>
) and thus behave like mutable reference types, cannot implementEquals
to mean reference equality (which would be the normal behavior for a mutable reference type) but generally use it to test equality of their transitory state. By contrast, if there were mutable and immutable box types, it would be possible for the immutable type to check equality of state, and the mutable one to check equality of reference.(4) An implicit cast from
T
toBox<T>
would not preclude the possibility of typeT
define an implicit cast to an interface type. By contrast, because all types are implicitly castable toObject
, neither vb.net nor C# will allow for the possibility of an implicit user cast between a struct type and an interface.(5) Without specialized compiler support for boxing, there would be no way for methods which presently accept a param-array of
Object[]
to automatically convert parameters from types likeInt32
toBox<Int32>
. On the other hand, adding a means of requesting that certain parameters be auto-boxed might be better than having implicit boxing everywhere. Note that if such a means existed, it could expecify that every parameter should be placed in aBox<T>
, thus making it possible to distinguish between passing aT
and aBox<T>
(since the latter would be passed as aBox<Box<T>>
.好的,您在这里涉及到几个主题。 首先,让我们看一下值类型以及它们存在的原因。 当您需要值语义时,您可以使用值类型:
例如,所有数字类型都是值类型,因为它们需要具有值语义。 如果变量 x 的值为 17 并且您将 x 分配给 y,则 y 将拥有其自己的值 17 且递增 y 不会将 x 更改为 18。因此,除非您有充分的理由,否则仅使用结构体 em> 当定义需要具有值语义的类型时。
在实现级别,通过使用内联分配来强制执行值语义。 您可以在此处了解更多相关信息。
这将我们引向拳击。 拳击比赛什么时候举行? 当您将值类型转换为引用类型时。 您使用了 object 类型作为示例,但对于 C# 泛型来说,这种情况在实践中应该很少发生。 更常见的情况是将值类型转换为接口; 例如,将 double 转换为 IEquatable 或 IComparable。 无论如何,如果将值类型转换为引用类型,装箱将会并且必须发生。
拳击发生时到底发生了什么? 制作要装箱的实例的副本并将其作为独立对象放置在堆上,以便即使原始实例超出范围也可以安全地引用它。 如果不是因为拳击,很容易让 CLR 尝试访问无效内存,我们都知道这是一件糟糕的事情。
那么,拳击到底是好是坏呢? 一方面它很好,因为它允许您在需要时安全地将值类型转换为引用类型。 另一方面,它会创建“垃圾”——被丢弃的短暂对象实例,并增加垃圾收集器必须完成的工作。 这很糟糕吗? 仅在某些情况下,比如开发XNA游戏。 如果您遇到这种情况,您将需要避免不受控制的拳击; 如果是这样,我还邀请您访问我的博客,我对此有一些一些建议主题。
Okay, there are several topics you're touching here. First of all, let's take a look at value types and why they exist. Value types are what you use when you need value semantics:
All numeric types, for example, are value types precisely because they need to have value semantics. If the variable x has value 17 and you assign x to y, then y will have its own value 17 and incrementing y won't change x to 18. Therefore, unless you have a good reason, use a struct only when defining a type that needs to have value semantics.
At the implementation level, value semantics are enforced by using in-line allocation. You can read more about it here.
This leads us to boxing. When does boxing happen? When you cast a value type into a reference type. You used the type object as an example, but with C# generics, that's something that should happen rarely in practice. A more frequent case would be to cast a value type into an interface; for example, casting a double into an IEquatable or IComparable. At any rate, if you cast a valu type into a reference type, boxing will and must occur.
What does really happen when boxing occurs? A copy of the instance to be boxed is made and placed on the heap, as an independent object, so that it can be safely referenced even when the original instance goes out of scope. If it wasn't for the boxing, it would be easy to get the CLR to try to access invalid memory and we all know that's a baaaaad thing.
So, is boxing good or bad? On the one hand it's good, because it allows you to safely cast value types into reference types when you need it. On the other hand, it creates "litter" -- short-lived instances of objects that get discarded and add to the work the garbage collector has to do. Is this bad? Only in some cases, such as developing XNA games. If this is your case, you'll want to avoid uncontrolled boxing; if so, I would also invite you to stop by my blog where I have some bits of advice on that topic.