引用类型总是从托管堆分配,值类型分配在栈中。
//引用类型(因为'class')
class SomeRef {
public Int32 x;
}
//值类型(因为'struct')
struct SomeVal {
public Int32 x;
}
static void ValueTypeDemo()
{
//前半部分
SomeRef rl = new SomeRef(); //在堆上分配
SomeVal vl = new SomeVal(); //在栈上分配
rl.x = 5; //提领指针
vl.x = 5; //在栈上修改
Console.WriteLine(rl.x); //显示"5"
Console.WriteLine(vl.x); //同样显示”5”
//后半部分
SomeRef r2 = rl; //只复制引用(指针)
SomeVal v2 = vl; //在栈上分配并复制成员
rl.x = 8; //rl.x和r2.x都会更改
vl.x = 9; //vl.x会更改,v2.x不变
Console.WriteLine(rl.x); //显示"8"
Console.WriteLine(r2.x); //显示"8"
Console.WriteLine(vl.x); //显示"9"
Console.WriteLine(v2.x); //显示"5"
}

设计自己的类型时,要仔细考虑类型是否应该定义成值类型而不是引用类型。值类型有时
能提供更好的性能。具体地说,除非满足以下全部条件,否则不应将类型声明为值类型。
//声明值类型
struct Point {
public Int32 x, y;
}
public sealed class Program {
public static void Main() {
ArrayList a = new ArrayList();
Point p; //分配一个Point (不在堆中分配)
for (Int32 i = 0; i < 10; i++) {
p.x = p.y = i; //初始化值类型中的成员
a.Add(p) ; //对值类型装箱,将引用添加到Arraylist中
}
}
}
public virtual Int32 Add(Object value);
装箱步骤:
假定要用以下代码获取ArrayList的第一个元素:
Point p = (Point)a [0];
第一步获取己装箱Point对象中的各个Point字段的地址。这个过 程称为拆箱(unboxing)o第二步将字段包含的值从堆复制到基于栈的值类型实例中。
拆箱的代价比装箱低得多。拆箱其实就是获取指针的过 程,该指针指向包含在一个对象中的原始值类型(数据字段)。其实,指针指向的是已装箱 实例中的未装箱部分。
一次拆箱操作经常紧接着一次字段复制。以下C#代码演示了拆箱和复制:
public static void Main() {
Point p;
p.x = p.y = 1;
Object o = p; //对p装箱;o引用已装箱实例
p = (Point)o; //对o拆箱.将字段从已装箱实例复制到栈变量中
}
最后一行,C#编译器生成一条1L指令对0拆箱(获取己装箱实例中的字段的地址),并生成另一条IL指令将这些字段从堆复制到基于栈的变量p中.
public static void Main()
{
Int32 v = 5; //创建未装箱值类型变量
Object o = v; // o引用已装箱的、包含值5的Int32
v = 123; //将未装箱的值修改成123
Console.WriteLine(v + ", " + (Int32) o) ; // 显示"123, 5"
}
.method public hidebysig static void Main () cil managed
{
.entrypoint
//代码大小 45 (0x2d)
.maxstack 3
.locals init ( [0]int32 v,[1] object o)
//将5加载到v中
IL_0000: ldc.i4.5
IL_0001: stloc.O
//对v装箱,将引用指针存储到o中
IL_0002: ldloc.O
IL_0003: box [mscorlib]System.Int32
IL_0008: stloc.1
//将123加载到v中
IL_0009: ldc.i4.s 123
IL_000b: stloc.O
//对v装箱,将指针保留在栈上以进行Concat (连接)操作
IL_000c: ldloc.O
IL_000d: box [mscorlib)System.Int32
//将字符串加载到栈上以执行Concat操作
IL_0012: ldstr ・・,"
//对o拆箱:获取一个指针,它指向栈上的Int32字段
IL_0017: ldloc.l
IL_0018: unbox.any [mscorlib]System.Int32
//对Int32装箱,将指针保留在栈上以进行Concat操作
IL_001d: box [mscorlib]System.Int32
// 调用 Concat
IL_0022: call string [mscorliblSystem.String::Conct(object,
object,
object)
//将从Concat返回的字符串传给WriteLine
IL_0027: call void [mscorlib]System.Console::WriteLine(string)
//从Main返回,终止应用程序
IL_002c: ret
} // end of method App::Main
Console.WriteLine(v +","+ o) ; // 显示"123, 5"
未装箱值类型比引用类型更“轻”。这要归结于以下两个原因。
using System;
internal struct Point : IComparable {
private Int32 m_x, m_y;
//构造器负责初始化字段
public Point(Int32 x, Int32 y)
{
m_x = x;
m_y = y;
}
// 觅写从 System.ValueType 继承的 ToString 方法
public override String ToString()
{
//将point作为字符串返回。注意:调用ToString以避免装箝
return String.Format(n({0}, (1})", m_x.ToString(), m_y.ToString());
}
//实现类型安全的Compare?o方法
public Int32 CompareTo(Point other)
{
//利用勾股定理计算哪个point距离原点(0, 0)更远
return Math.Sign(Math.Sqrt(m_x * m_x + m_y * m_y)- Math.Sqrt(other.ra_x * other,m_x + other.m_y * other,m_y));
}
// 实现 IComparable 的 CompareTo 方法
public Int32 CompareTo(Object o) {
if (GetType() != o.GetType()) {
throw new ArgumentException(no is not a Point");
}
//调用类型安全的CompareTo方法
return CompareTo((Point) o);
}
}
public static class Program
{
public static void Main()
{
//在栈上创建两个Point实例
Point pl = new Point(10r,10);
Point p2 = new Point(20, 20);
//调用ToString (虚方法)不装箱pl
Console. WriteLine(pl.ToString() ); // 显示"(10, 10) n
//调用GetType (非虚方法)时.要对pl进行装箱
Console. WriteLine(pl. GetType () ) ; // 显示"Point"
//调用CompareTo不装箱pl
//山于调用的是CompareTo(Point).所以p2不装箱
Console. WriteLine(pl. CompareTo(p2) ) ; // 显示"-1"
// pl要装箱,引用放到c中
IComparable c = pl;
Console.WriteLine(c.GetType() ) ; // 显示"Point"
//调用CompareTo不装箱pl
//由于向CompareTo传递的不是Point变量,
//所以调用的是CompareTo(Object),它要求获取对己装箱Point的引用
// c不装箱是因为它本来就引用已装箱Point
Console.WriteLine(pl. CompareTo(c) ) ; // 显不"0"
// c不装箱,因为它本來就引用己装箱Point
// p2要装箱,因为调用的是CompareTo(Object)
Console.WriteLine(c.CompareTo(p2) ) ; // 显不"-1"
//对c拆箱,字段复制到p2中
p2 = (Point) c;
//证明字段己复制到p2中
Console.WriteLine (p2 .ToString () ) ; // 显示"(10, 10)"
}
}