装箱和拆箱的工作原理 - 模拟模型





5.00/5 (2投票s)
一种解释装箱和拆箱的新方法
引言
首先,需要说明撰写本文的目的。经验丰富的程序员了解装箱和拆箱的工作原理,以及如何解决与这些过程相关的可能问题。然而,要向初学者成功解释这些过程,需要他们理解额外的技术。因此,IL、堆栈、堆等对他们来说可能很难理解。
开发人员应该理解装箱和拆箱,以避免在.NET平台软件开发中特定情况下可能出现的负面副作用。装箱和拆箱会增加内存消耗并降低性能。此外,这些过程通常是隐藏的,程序员无法明确控制它们。
互联网和书籍中有很多很好的解释材料。本文尝试创建一个新的额外概念,以向初学者解释装箱和拆箱的机制。其思想是使用简单的C#代码进行模拟。
建议创建一个程序,以清晰明确的层面模拟装箱和拆箱的过程。尽管创建完全符合现实的模型是不可能的,但可以很容易地构建一个能够展示所考虑过程关键特征的模型。就其本质而言,模拟简化了实际情况,因此在许多情况下,与现实的差异并非缺陷。
COM 技术
C# 面向对象编程和变量模型受到以前开发的 COM 模型的影响。尽管 COM 不是本文的主题,但仍应提及 COM 的一些特性
- COM 是一种通用的支持面向对象编程的技术。
- COM 不依赖于实现(或使用)的编程语言。因此,COM 具有二进制格式。
- COM 具有反映 C++ 类内部表示的二进制格式。因此,COM 组件是类,而不是结构(尽管 C++ 中的类和结构非常相似,但它们并不相同)。
- COM 组件只能通过接口访问。
- COM 组件通过包含和聚合支持继承。
- COM 组件通过接口支持多态性。
- COM 是二进制结构,其内部无法访问或修改。
COM 对 C# 面向对象编程和变量模型的影响是
- 类只能通过引用或接口访问。
- 结构不是类。由于在安全代码中禁止使用指针,因此结构是直接访问的,而不是通过引用或接口访问的。
- 结构可以包装成类(成为对象)。这些包装器是对象,可以通过引用或接口访问。这种神奇的操作称为装箱。包装的结构可以拆开,换句话说,可以拆箱回结构。
- 结构提供了更高的性能和更小的内存使用。类则支持 OOP/OOD。装箱/拆箱可以被视为连接这两种结构和可能性的桥梁。
欲了解更多详情,您还可以参阅以下资料
- 继承、聚合和包含
- “Programming Visual C++”,Microsoft Press,1998
模拟
正如维基百科所说:“模拟是对真实世界过程或系统随时间运行的模仿。模拟某事首先需要开发一个模型;该模型代表所选物理或抽象系统或过程的关键特征或行为/功能。模型代表系统本身,而模拟则代表系统随时间的运行。”(参见模拟 https://en.wikipedia.org/wiki/Simulation)。
为了理解为什么我们需要一个模型,我们应该注意,装箱和拆箱是.NET的内部过程,不能由程序员直接控制。程序员只能通过使用或避免某些C#代码来隐式控制装箱或拆箱何时发生。因此,模拟的主要目的是根据所使用的C#代码来展示隐藏的内部行为。
系统模型
可以构建一个简单的模型来模拟 `int` 类型的装箱和拆箱。选择 `int` 类型进行建模是因为它是广泛使用的简单类型。另一种要建模的类型是 `object`。
装箱和拆箱由编译器和运行时控制和实现。编译器和运行时执行一些神奇的操作来支持装箱和拆箱。所有的细节都不应该被建模,因为这是一项非常复杂的任务。同时,编译器和运行时的“魔法”可以通过标准代码流和模拟之间的差异来解释(参见下面的用例章节)。
具有这些假设的系统将包含三个组件
int
类型模型object
类型模型- 编译器和运行时——为了避免模型过于复杂,将不进行建模
int 的模型
因为 `int` 是值类型,所以我们创建一个同样是结构的 `IntValue` 类型。 `IntValue` 类型将模拟 `int` 类型。代码包含注释。请参阅下面的用例以更好地理解如何使用代码。
同时,应强调以下特点
- 在底层,结构位于堆栈(如果由 JIT 优化,则位于 CPU 寄存器),而不是堆中。结构也直接访问,而不是通过引用(因此,它们被称为值类型)。所以结构不能实现接口。因此,我们将 `CompareTo` 方法实现为一个简单的成员。(我们不能在这里将其实现为 `IComparable.CompareTo`,因为它会破坏模拟模型并将装箱放入编译器“魔法”中)。
// Models type int for int boxing/unboxing
// Mimics behaviour of int
public struct IntValue
{
// Stores actual value
private int _v;
// Constructs the structure
public IntValue(int value)
{
_v = value;
}
// Implicitly converts int to IntValue
// Need to compile lines such as
// IntValue a = 83;
// int b = 21;
// IntValue c = b;
public static implicit operator IntValue(int value)
{
return new IntValue(value);
}
// Implicitly converts IntValue to int
// Need to compile lines such as
// IntValue a = 83;
// int b = a;
public static implicit operator int(IntValue value)
{
return value._v;
}
// Output value to string
public override string ToString()
{
return _v.ToString();
}
// Compares value to obj
// Is implemented as a single method
// but not as a method of IComparable
public int CompareTo(object obj)
{
if (obj == null)
return 1;
if (obj is IntValue)
{
IntValue iv = (IntValue)obj;
if (_v < iv) return -1;
if (_v > iv) return 1;
return 0;
}
throw new System.ArgumentException("Type must be compatible with IntValue");
}
}
Object 的模型
因为 `object` 是一个引用类型,所以我们创建一个类 `IntObject`。 `IntObject` 类型模拟装箱到 `object` 的 `int` 类型。代码包含注释。请参阅下面的用例以更好地理解代码。
同时,应强调以下特点
- 装箱的模拟被建模为从 `IntValue` 到 `IntObject` 的转换。因为装箱是一个隐式过程,所以它通过隐式运算符建模。
- 拆箱的模拟被建模为从 `IntObject` 到 `IntValue` 的转换。因为拆箱是一个显式过程,所以它通过显式运算符建模。
- 装箱类型在内部(底层)是 `object` 类型的构造。但它们返回包装值的类型。因此 `GetType()` 实现了这一逻辑。
- 类位于堆中并通过引用访问。因此,它们可以原生实现并提供接口(参见上面的COM 技术)。通过这种方式,`IntObject` 通过 `IComparable.CompareTo` 调用 `IntValue` 的简单成员 `CompareTo` 以标准方式实现。
IntObject
专门处理 int
类型。这种限制简化了模型。尽管 object
对所有值类型都是通用的,我们也可以为 object
构建通用模型,但开发和解释装箱和拆箱会过于复杂。
// Models type Object for int boxing/unboxing modelling
// Is wrapper for type IntValue that models int
public class IntObject: IComparable
{
// Wrapped value type
private IntValue _v;
// Constructor from IntValue is private
private IntObject(IntValue value)
{
_v = value;
}
// Conversion from IntValue to IntObject
// mimics boxing (implicit process).
public static implicit operator IntObject(IntValue value)
{
Console.WriteLine("Boxing " + value.ToString());
return new IntObject(value);
}
// Conversion from IntObject to IntValue
// mimics unboxing (explicit process).
public static explicit operator IntValue(IntObject value)
{
Console.WriteLine("UnBoxing " + value._v.ToString());
return value._v;
}
// Returns Type of wrapped type
public Type GetType()
{
return _v.GetType();
}
// Implementation of IComparable.CompareTo method
public int CompareTo(object obj)
{
return _v.CompareTo(obj);
}
}
用例
装箱和拆箱可以通过以下技术进行研究
- 将所有 `int` 条目替换为 `IntValue`
- 将所有 `object` 条目替换为 `IntObject`
- 运行代码或在调试模式下逐行跟踪代码
用例 1
简单的装箱和拆箱可以通过以下代码实现
int i = 9;
object o = i;
int i2 = (int)o;
代码可以建模为
IntValue i = 9;
IntObject o = i;
IntValue i2 = (IntValue)o;
输出
Boxing 9
UnBoxing 9
用例 2
当使用容器时,装箱和拆箱可以通过以下代码实现
List<object> l = new List<object>();
for (int ii = 0; ii < 10; ii++)
l.Add(ii);
int sum = 0;
for (int ii = 0; ii < l.Count; ii++)
sum += (int)l[ii];
Console.WriteLine(sum);
代码可以建模为
List<IntObject> l = new List<IntObject>();
for (IntValue ii = 0; ii < 10; ii++)
l.Add(ii);
int sum = 0;
for (IntValue ii = 0; ii < l.Count; ii++)
sum += (IntValue)l[ii];
Console.WriteLine(sum);
输出
Boxing 0
Boxing 1
Boxing 2
Boxing 3
Boxing 4
Boxing 5
Boxing 6
Boxing 7
Boxing 8
Boxing 9
UnBoxing 0
UnBoxing 1
UnBoxing 2
UnBoxing 3
UnBoxing 4
UnBoxing 5
UnBoxing 6
UnBoxing 7
UnBoxing 8
UnBoxing 9
45
用例 3
原始值和装箱值的类型研究可以通过以下代码实现
int i3 = 37;
object o3 = i3;
Console.WriteLine(i3.GetType() + " " + o3.GetType());
代码可以建模为
IntValue i3 = 37;
IntObject o3 = i3;
Console.WriteLine(i3.GetType() + " " + o3.GetType());
输出
Boxing 37
BoxingModel.IntValue BoxingModel.IntValue
用例 4
研究如何从原始类型获取接口可以通过以下代码实现
int i4a = 495;
IComparable ic4 = (IComparable)i4a;
int i4b = 384;
Console.WriteLine("495 compared to {0} gives {1}", i4b, ic4.CompareTo(i4b));
代码可以建模为
IntValue i4a = 495;
// IComparable ic4 = (IComparable)i4a; // Cannot directly get interface from structure
IComparable ic4 = (IntObject)i4a; // Boxing needs. Compiler implements this magic implicitly.
IntValue i4b = 384;
Console.WriteLine("495 compared to {0} gives {1}", i4b, ic4.CompareTo(i4b));
输出
Boxing 495
495 compared to 384 gives 1
结论
该模型并非完美无缺,但其主要思想是引入一种新的教学方法,通过使用更高抽象度的编程语言结构来模拟隐藏的隐式过程。该模型可以扩展,以包含对其他功能和用例的解释。