C# 装箱和拆箱:什么时候开始?

C# 装箱和拆箱:什么时候开始?,c#,object,boxing,C#,Object,Boxing,所以我明白什么是装箱和拆箱。它什么时候出现在现实世界的代码中,或者在什么例子中是一个问题?我无法想象做这样的事情: int i = 123; object o = i; // Boxing int j = (int)o; // Unboxing …但这几乎肯定是过于简单化了,我甚至可能在不知情的情况下进行装箱/拆箱。装箱(以我的经验)通常发生在以下情况: 将值类型传递给接受类型为Object的参数的方法 将值类型添加到非泛型集合(如ArrayList) 您还可以

所以我明白什么是装箱和拆箱。它什么时候出现在现实世界的代码中,或者在什么例子中是一个问题?我无法想象做这样的事情:

int i = 123;
object o = i;           // Boxing
int j = (int)o;     // Unboxing
…但这几乎肯定是过于简单化了,我甚至可能在不知情的情况下进行装箱/拆箱。

装箱(以我的经验)通常发生在以下情况:

  • 将值类型传递给接受类型为
    Object
    的参数的方法
  • 将值类型添加到非泛型集合(如
    ArrayList
您还可以看到装箱和取消装箱是在使用反射时发生的,因为.NET framework的反射API大量使用
对象

,与泛型之前相比,现在的问题要小得多。例如,现在我们可以使用:

List<int> x = new List<int>();
x.Add(10);
int y = x[0];
至少,那是我最常见的拳击和拆箱经验


如果不涉及泛型,我想我可能会说,在我所从事的项目中,反射是最常见的装箱原因。反射API总是使用“对象”来表示方法的返回值,因为它们没有其他方法知道要使用什么


如果您不知道,另一个可能会让您感到困惑的原因是,您使用了一个实现接口的值类型,并将该值传递给另一个将接口类型作为其参数的方法。同样,泛型使这一问题变得不那么严重,但如果您没有意识到这一点,这可能会是一个令人讨厌的惊喜。

装箱和拆箱实际上正在从值类型转移到引用类型。因此,可以将其视为从堆栈移动到堆,然后再移动回来


当然,在某些情况下,这是相关的。在2.0框架中包含泛型使许多常见的装箱不再实用。

自从C#2.0(Visual Studio 2005)中出现使用泛型的强类型列表和字典以来,我认为将装箱/拆箱牢记在心的重要性已经惊人地降到了最低。加上可空类型(
int?
,等等)并使用空合并操作符(
),它真的不应该引起太大的关注,并且可能不会在任何不是1.1框架或更早版本的代码中看到它。

当值类型(如struct、int、long)发生装箱/取消装箱传递到接受引用类型的某个位置,例如
对象

当您显式创建一个方法,该方法接受将传递给值类型的object类型的参数时,就会发生这种情况。当您使用较旧的非泛型集合存储值类型(通常是原语)时,也会出现这种情况

当您使用
String.Format()
并向其传递原语时,还会看到装箱。这是因为
String.Format()

使用反射调用方法还可能导致装箱/取消装箱,因为反射API别无选择,只能返回
对象
,因为编译时不知道实际类型(反射API不能是泛型的)

较新的泛型集合不会导致装箱/取消装箱,因此出于这个原因,较旧的集合更可取(例如ArrayList、Hashtable等)。更不用说它们是类型安全的

通过更改接受对象为泛型的方法,可以避免装箱问题。例如:

public void string Decorate( object a ) // passing a value type results in boxing
{
   return a.ToString() + " Some other value";
}
vs:

公共空字符串装饰(TA)
{
返回a.ToString()+“其他值”;
}

当人们不知道这意味着什么,只是不在乎,或者有时人们会情不自禁地接受拳击是次要水平时,就会发生这种情况

当您访问值类型属性时,强类型数据行几乎总是会装箱/取消装箱。
此外,使用值类型作为接口引用也会将其装箱。或者从值类型的实例方法获取委托。(委托的目标是Object类型)

这里有一个非常讨厌的目标:)


我同意你的看法。从我所读到的关于这个问题的一切来看,我就是不明白重点。我们需要注意:int(Int32)是抽象类ValueType的一个子类,它是Object的一个子类。我不会说它是“子类”——它根本不是类。它继承自(或派生自)
ValueType
。C#和CLI规范在这方面使用了稍微不同的术语,这对问题没有帮助。“我想我可能会说,在我所从事的项目中,反射是最常见的装箱原因”——当然,这在很大程度上取决于项目的类型。例如,如果使用WPF或Silverlight,则在使用值转换器(IValueConverter获取并返回对象)、依赖项属性(DependencyObject.GetValue和SetValue返回并获取对象)时,装箱将一直发生,等等,+1用于在值类型上实现的接口-这是一个狡猾的例子:)@itowlson:这些都是很好的例子-介意我将它们添加到我的答案中吗?可以安全地说,装箱是“将某个对象强制转换为某个对象”,而拆箱是“将其强制转换回其类型”@JYelton:我认为这有点过于笼统和具体。首先,它只适用于值类型。其次,每当您将值类型值转换为引用类型值时,就会发生这种情况:一个接口、
Object
ValueType
Enum
。当您将unconstraint Ts与null进行比较时,C#将发出.box。我同意这一点——但这是开发人员在实现泛型方法/类时的决定。从框架的角度来看,通用集合允许使用者放弃装箱/拆箱场景。当我编写您描述的场景时,我的无约束T将与默认值(T)进行比较,而不是null。如果我真的需要检查null,那就更好了
public void string Decorate( object a ) // passing a value type results in boxing
{
   return a.ToString() + " Some other value";
}
public void string Decorate<T>( T a )
{
   return a.ToString() + " some other value";
}
SqlCommand cmd = <a command that returns a scalar value stored as int>;

// This code works very well.
int result = (int)cmd.ExecuteScalar();

// This code will throw an exception.
uint result = (uint)cmd.ExecuteScalar();
uint result = (uint)(int)cmd.ExecuteScalar();