为什么在c#中需要装箱和拆箱?
我知道什么是装箱和开箱,但我不理解它的真正用途。为什么,在哪里使用?
short s = 25;
object objshort = s; //Boxing
short anothershort = (short)objshort; //Unboxing
为什么在c#中需要装箱和拆箱?
我知道什么是装箱和开箱,但我不理解它的真正用途。为什么,在哪里使用?
short s = 25;
object objshort = s; //Boxing
short anothershort = (short)objshort; //Unboxing
当前回答
当将值类型传递给具有对象类型的变量或参数时,就会发生装箱。因为它是自动发生的,所以问题不是什么时候应该使用装箱,而是什么时候应该使用类型对象。
类型对象应该只在绝对必要时使用,因为它规避了类型安全,而类型安全是c#等静态类型语言的主要好处。但是在不可能在编译时知道值的类型的情况下,它可能是必要的。
例如,当通过ADO读取数据库字段值时。净框架。返回值可以是整数、字符串或其他东西,因此类型必须是object,客户机代码必须执行适当的类型转换。为了避免这个问题,像Linq-to-SQL或EF Core这样的ORM框架使用静态类型的实体,这样就避免了object的使用。
在引入泛型之前,像ArrayList这样的集合以项目类型为对象。这意味着您可以在列表中存储任何内容,并且可以向数字列表中添加字符串,而不会引起类型系统的抱怨。泛型解决了这个问题,在使用值类型集合时不再需要装箱。
所以很少需要把东西输入为object,你想要避免它。在代码需要同时处理值类型和引用类型的情况下,泛型通常是更好的解决方案。
其他回答
当我们有一个需要object作为参数的函数,但我们有不同的值类型需要传递时,装箱是必需的,在这种情况下,我们需要在将值类型传递给函数之前首先将值类型转换为对象数据类型。
我不认为这是真的,试试这个:
class Program
{
static void Main(string[] args)
{
int x = 4;
test(x);
}
static void test(object o)
{
Console.WriteLine(o.ToString());
}
}
这运行得很好,我没有使用装箱/拆箱。(除非编译器在幕后做这些?)
在. net框架中,有两种类型——值类型和引用类型。这在面向对象语言中比较常见。
面向对象语言的一个重要特性是以类型不可知的方式处理实例的能力。这被称为多态性。由于我们希望利用多态性,但我们有两种不同的类型,因此必须有某种方法将它们组合在一起,以便以相同的方式处理其中一个或另一个。
现在,回到过去(Microsoft.NET 1.0),还没有这种新奇的泛型喧闹。您不能编写一个方法,它只有一个参数,可以服务于值类型和引用类型。这违反了多态性。因此,采用装箱作为一种将值类型强制转换为对象的方法。
如果这是不可能的,那么框架中就会充斥着方法和类,它们的唯一目的就是接受其他类型。不仅如此,由于值类型并没有真正共享一个共同的类型祖先,因此必须为每种值类型(比特、字节、int16、int32等等)设置不同的方法重载。
拳击阻止了这种情况的发生。这就是英国人庆祝节礼日的原因。
当一个方法只接受一个引用类型作为参数时(比如一个泛型方法通过new约束被约束为一个类),您将不能将引用类型传递给它,而必须对它进行装箱。
对于任何以object作为参数的方法也是如此——这必须是引用类型。
Boxing和Unboxing专门用于将值类型对象视为引用类型;将它们的实际值移动到托管堆并通过引用访问它们的值。
如果不进行装箱和拆箱,就无法通过引用传递值类型;这意味着您不能将值类型作为Object的实例传递。
为什么
拥有统一的类型系统,并允许值类型以与引用类型完全不同的方式表示其基础数据(例如,int只是一个32位的桶,这与引用类型完全不同)。
这样想。你有一个object类型的变量o。现在你有一个int型,你想把它变成o。o是对某处某物的引用,而int型显然不是对某处某物的引用(毕竟,它只是一个数字)。所以,你要做的是:创建一个可以存储int的新对象,然后将该对象的引用赋值给o。我们称这个过程为“装箱”。
所以,如果你不关心是否有一个统一的类型系统(例如,引用类型和值类型有非常不同的表示,你不想用一种通用的方式来“表示”这两者),那么你就不需要装箱。如果你不关心用int表示它们的底层值(也就是说,用int作为引用类型,只存储对它们底层值的引用),那么你不需要装箱。
我应该在哪里使用它。
例如,旧的集合类型ArrayList只包含对象。也就是说,它只存储对某个地方的某个对象的引用。如果没有装箱,就不能在这样的集合中放入int型。但在拳击中,你可以。
现在,在泛型的时代,你并不真的需要这个,通常可以轻松地进行,而不需要考虑这个问题。但有一些注意事项需要注意:
这是正确的:
double e = 2.718281828459045;
int ee = (int)e;
这不是:
double e = 2.718281828459045;
object o = e; // box
int ee = (int)o; // runtime exception
相反,你必须这样做:
double e = 2.718281828459045;
object o = e; // box
int ee = (int)(double)o;
首先,我们必须显式地打开double ((double)o),然后将其强制转换为int型。
下面的结果是什么:
double e = 2.718281828459045;
double d = e;
object o1 = d;
object o2 = e;
Console.WriteLine(d == e);
Console.WriteLine(o1 == o2);
下句话之前先想一下。
如果你说真假太棒了!等等,什么?这是因为引用类型上的==使用引用相等来检查引用是否相等,而不是检查底层值是否相等。这是一个很容易犯的危险错误。也许更微妙
double e = 2.718281828459045;
object o1 = e;
object o2 = e;
Console.WriteLine(o1 == o2);
也会打印False!
更好的说法是:
Console.WriteLine(o1.Equals(o2));
然后,谢天谢地,它会打印True。
最后一个微妙之处:
[struct|class] Point {
public int x, y;
public Point(int x, int y) {
this.x = x;
this.y = y;
}
}
Point p = new Point(1, 1);
object o = p;
p.x = 2;
Console.WriteLine(((Point)o).x);
输出是什么?视情况而定!如果Point是一个结构体,则输出为1,但如果Point是一个类,则输出为2!装箱转换生成被装箱的值的副本,以解释行为上的差异。