烟台龙口网站建设,商城网站的基本功能,crm客户关系管理系统源码,网站办事服务建设情况.NET大牛之路 • 王亮精致码农 • 2021.08.27前面我们讲到 .NET 平台支持的两大数据类型#xff1a;值类型和引用类型。值类型比引用类型更高效#xff0c;因为它没有指针引用#xff0c;不用分配在托管堆中#xff0c;也不用被 GC 回收。但有时候你可能偶尔需要将一种类型… .NET大牛之路 • 王亮精致码农 • 2021.08.27前面我们讲到 .NET 平台支持的两大数据类型值类型和引用类型。值类型比引用类型更高效因为它没有指针引用不用分配在托管堆中也不用被 GC 回收。但有时候你可能偶尔需要将一种类型的变量表示为另一种类型的变量。为此C# 提供了装箱和拆箱的机制。1理解装箱简单地说装箱就是将一个值类型的数据存储在一个引用类型的变量中。假设你一个方法中创建了一个 int 类型的本地变量你要将这个值类型表示为一个引用类型那么就表示你对这个值进行了装箱操作如下所示static void SimpleBox()
{int myInt 25;// 装箱操作object boxedInt myInt;
}确切地说装箱的过程就是将一个值类型分配给 Object 类型变量的过程。当你装箱一个值时CoreCLR 会在堆上分配一个新的对象并将该值类型的值复制到该对象实例。返回给你的是一个在托管堆中新分配的对象的引用。2理解拆箱反过来将 Object 引用类型变量的值转换回栈中相应的值类型的过程则称为拆箱。从语法上讲拆箱操作看起来就像一个正常的转换操作。然而其语义是完全不同的。CoreCLR 首先验证接收的数据类型是否等同于被装箱的类型如果是它就把值复制回基于栈存储的本地变量中。例如如果下面的 boxedInt 的底层类型确实是 int那就完成了拆箱操作static void SimpleBoxUnbox()
{int myInt 25;// 装箱操作object boxedInt myInt;// 拆箱操作int unboxedInt (int)boxedInt;
}记住与执行典型的类型转换不同你必须将其拆箱到一个恰当的数据类型中。如果你试图将一块数据拆箱到不正确的数据类型中将会抛出 InvalidCastException 异常。为了安全起见如果你不能保证 Object 类型背后的类型最好使用 try/catch 逻辑把拆箱操作包起来尽管这样会有些麻烦。考虑下面的代码它将抛出一个错误因为你正试图将装箱的 int 类型拆箱成一个 long 类型static void SimpleBoxUnbox()
{int myInt 25;// 装箱操作object boxedInt myInt;// 拆箱到错误的数据类型将触发运行时异常try{long unboxedLong (long)boxedInt;}catch (InvalidCastException ex){Console.WriteLine(ex.Message);}
}3生成的 IL 代码当 C# 编译器遇到装箱/拆箱语法时它会生成包含装箱/拆箱操作的 IL 代码。如果你用 ildasm.exe 查看编译的程序集你会看到装箱和拆箱操作对应的 box 和 unbox 指令.method assembly hidebysig staticvoid Main$g__SimpleBoxUnbox|0_0() cil managed
{.maxstack 1.locals init (int32 V_0, object V_1, int32 V_2)IL_0000: nopIL_0001: ldc.i4.s 25IL_0003: stloc.0IL_0004: ldloc.0IL_0005: box [System.Runtime]System.Int32IL_000a: stloc.1IL_000b: ldloc.1IL_000c: unbox.any [System.Runtime]System.Int32IL_0011: stloc.2IL_0012: ret} // end of method Program$::Main$g__SimpleBoxUnbox|0_0乍一看装箱/拆箱似乎是一个没啥用的语言特性学术性大于实用性。毕竟你很少需要在一个本地 Object 变量中存储一个本地值类型。然而事实是装箱/解箱过程是相当有用的因为它允许你假设一切都可以被当作 Object 类型来处理而 CoreCLR 会自动帮你处理与内存有关的细节。4实际应用让我们来看看装箱/拆箱的实际应用我们以 C# 的 ArrayList 类为例用它来保存一批在栈中存储的整型数据。ArrayList 类的相关方法成员列举如下public class ArrayList : IList, ICloneable
{...public virtual int Add(object? value);public virtual void Insert(int index, object? value);public virtual void Remove(object? obj);public virtual object? this[int index] { get; set; }
}请注意上面 ArrayList 的方法都是对 Object 类型数据进行操作。ArrayList 是为操作对象代表任何类型而设计的而对象是在托管堆上分配的数据。请考虑下面代码static void WorkWithArrayList()
{// 当传递给对象的方法时值类型会自动被装箱ArrayList myInts new ArrayList();myInts.Add(10);
}尽管你直接将数字数据传入需要 Object 参数的方法中但运行时自动将分配在栈中的数据装箱。如果你想使用索引器从 ArrayList 中检索一条数据你必须使用转换操作将堆分配的对象拆箱为栈分配的整型因为 ArrayList 的索引器返回的是 Object 类型而不是 int 类型。static void WorkWithArrayList()
{// 当传递给需要对象参数的方法时值类型就自动被装箱ArrayList myInts new ArrayList();myInts.Add(10);// 当对象被转换回基于栈存储的数据时就会发生拆箱int i (int)myInts[0];// 由于 WriteLine() 需要的 object 参数又重新装箱了Console.WriteLine(Value of your int: {0}, i);
}在调用 ArrayList.Add() 之前在栈中分配的 int 数值被装箱了所以它可以被传入参数为 Object 类型的方法中。从 ArrayList 中检索到 Object 类型的数据时通过转换操作它就被拆箱成 int 类型。最后当它被传递给 Console.WriteLine() 方法时又被装箱了因为这个方法的参数是 Object 类型。5小结从程序员的角度来看装箱和拆箱是很方便的我们不需要手动去复制和转移内存中的值类型和引用类型的数据。但装箱和拆箱背后的栈/堆内存转移也带来了性能问题。下面总结一下对一个简单的整型数进行装箱和拆箱所需要的步骤在托管堆中分配一个新对象在栈中的数据值被转移到该托管堆中的对象上当拆箱时存储在堆中对象上的值被转移回栈中堆上未使用的对象将最终被 GC 回收。尽管很多时候装箱和拆箱操作不会在性能方面造成重大影响但如果一个像 ArrayList 这样的集合包含成千上万条数据而你的程序又会频繁操作这些数据性能的影响还是会很明显的。所以我们平时在编程时应当尽量避免发生装箱和拆箱操作。比如对于上面 ArrayList 的示例如果集合元素类型是一致的则应当使用泛型的集合类型比如改用 ListT、LinkedListT 等。