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.Object的抽象类System.ValueType。您无法从C#中的System.ValueType派生,但我猜想struct关键字确实如此,并且CLI将这些类型定义识别为具有按值传递的语义。无论如何,当一个struct被分配给对象拳击的类型时。我不想陷入拳击本身,而是我想直接进入它。
我查看了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;
}
答案 0 :(得分:2)
我不完全确定你的问题。你只是问你的解决方案是否比普通拳击更好?它当然有一些吸引力。如果你问为什么拳击没有首先以这种方式实现,请记住.NET没有开头的泛型。
编辑:无论如何,拳击是非常罕见的泛型。不要忘记,如果对类型实例的引用作为object
传递,那么你仍然需要进行运行时强制转换(无论如何拳击都是如此)。另外不要忘记接口 - 如果值类型实现了接口,那么它用于装箱的相应引用类型也是如此。你的解决方案不会删除拳击的使用,因为你不能让你的类型“假装”实现界面。 (你或许能够对DLR做些什么,但到那时大部分时间都已丢失了:)
答案 1 :(得分:2)
我们认为更快更完全无关紧要。在考虑更快的时候,只有剖析器是相关的。
答案 2 :(得分:1)
“如果我打算传递相同的值 作为一个对象,我真的想要 每次都是unbox / box?“
简短回答:不,你不想做很多拳击/拆箱。它会产生开销:额外的额外垃圾并且往往很慢(尽管我认为速度已经在以后的框架版本中进行了优化)。
编辑:但是,如果你“将一个相同的值传递给一个对象”,那么在没有强制转换的情况下,它会返回到值类型,直到它需要为止,然后它会保持整个盒子的状态,而不会被取消装箱。
但是,正如大家所说,无论如何,你不需要“将相同的值传递给对象”。除非您正在使用Framework 1.x,否则这就是泛型的用途。当BCL集合类使用System.Object并且输入的任何值类型被装箱时,Boxing更具相关性。
(另外,如果通过接口访问,则盒装值类型不会被取消装箱。)
答案 3 :(得分:1)
你的问题标题错过了我认为最有趣的方面:系统的拳击行为与Box<T>
类型不同。有一些差异:
(1)盒装T
将使用相同的代码实现与T
相同的接口,但主要使用类语义而不是值语义,但具有古怪的{{1}方法。
(2)对盒装Equals
进行变异通常会在C#或vb.net中产生麻烦,但是盒装T
永远不会真正不可变,因为不允许可验证的代码可以执行此操作,即使在某些语言中它很尴尬(在C ++ / CLI中很容易)。盒装T
时甚至类型也是可变的。相比之下,可以定义一个Int32
,它采用类型为ImmutableBox<T>
的构造函数,其字段将是真正不可变的。
(3)即使是盒装时易于变异的结构类型(例如因为它们实现了像T
这样的变异接口)并因此表现得像可变引用类型,也无法实现IEnumerator<T>
来表示引用相等(这可能是可变引用类型的正常行为)但通常用它来测试它们的暂时状态的相等性。相比之下,如果存在可变和不可变的框类型,则不可变类型可以检查状态的相等性,而可变类型则可以检查引用的相等性。
(4)从Equals
到T
的隐式强制转换不排除类型Box<T>
定义对接口类型的隐式强制转换的可能性。相比之下,因为所有类型都可以隐式转换为T
,所以vb.net和C#都不允许在结构类型和接口之间进行隐式用户转换。
(5)如果没有针对装箱的专门编译器支持,那么目前接受Object
的参数数组的方法无法自动将参数从Object[]
转换为Int32
}。另一方面,添加一种请求某些参数被自动装箱的方法可能比在任何地方进行隐式装箱更好。请注意,如果存在这样的方法,则可以预先指出每个参数都应放在Box<Int32>
中,从而可以区分传递Box<T>
和T
(因为后者)将作为Box<T>
传递。
答案 4 :(得分:0)
好的,你在这里有几个主题。首先,让我们来看看价值类型及其存在的原因。值类型是您在需要值语义时使用的内容:
对于课程,可以有两个课程 变量引用相同 对象,因而可能 对一个变量的操作影响 另一个引用的对象 变量。结构,变量 每个都有自己的数据副本 (在ref和out的情况下除外) 参数变量),而不是 可以对一个人进行操作 影响对方。而且,因为 结构不是引用类型,它是 不可能是结构的值 type为null。
例如,所有数值类型都是值类型,因为它们需要具有值语义。如果变量 x 的值为17,并且您将 x 分配给 y ,则 y 将使其拥有值17并递增 y 不会将 x 更改为18.因此,除非您有充分的理由,否则请仅使用struct 在定义需要具有值语义的类型时。
在实现级别,通过使用内联分配强制执行值语义。您可以阅读更多相关信息here。
这导致我们拳击。什么时候拳击发生?将值类型转换为引用类型时。您使用类型对象作为示例,但使用C#泛型,这在实践中很少发生。更常见的情况是将值类型转换为接口;例如,将双转换为 IEquatable 或 IComparable 。无论如何,如果你将一个值类型转换为引用类型,那么就必须进行装箱。
拳击发生时会发生什么?将要装箱的实例的副本作为独立对象放在堆上,以便即使原始实例超出范围也可以安全地引用它。如果不是拳击,那么很容易让CLR尝试访问无效的内存,我们都知道这是一个很好的事情。
那么,拳击好还是坏?一方面它很好,因为它允许您在需要时安全地将值类型转换为引用类型。另一方面,它创建了“垃圾” - 被丢弃的对象的短期实例,并添加到垃圾收集器必须完成的工作中。这不好吗?仅限于some cases,例如开发XNA游戏。如果这是你的情况,你会想避免不受控制的拳击;如果是这样,我也会邀请你到我的博客站点,我有一些bits of advice on that topic。