原文如下:类与结构有很多相似之处:结构可以实现接口,并且可以具有与类相同的成员类型。然而,结构在几个重要方面不同于类:结构为值类型而不是引用类型,并且结构不支持继承。结构的值存储在“在堆栈上”或“内联”。细心的程序员有时可以通过聪明地使用结构来增强性能。例如,将 Point 定义为结构而不是类在运行时可以节省很多内存空间。下面的程序创建并初始化一个 100 点的数组。对于作为类实现的 Point,出现了 101 个实例对象,因为数组需要一个,它的 100 个元素每个都需要一个。class Point
{
   public int x, y;
   public Point(int x, int y) {
      this.x = x;
      this.y = y;
   }
}
class Test
{
   static void Main() {
      Point[] points = new Point[100];
      for (int i = 0; i < 100; i++)
         points[i] = new Point(i, i*i);
   }
}
如果将 Point 改为作为结构实现,如struct Point
{
   public int x, y;
   public Point(int x, int y) {
      this.x = x;
      this.y = y;
   }
}
则只出现一个实例对象(用于数组的对象)。Point 实例在数组中内联分配。此优化可能会被误用。使用结构而不是类还会使应用程序运行得更慢或占用更多的内存,因为将结构实例作为值参数传递会导致创建结构的副本。
为什么会创建101个呢?什么是内联分配呢

解决方案 »

  1.   

    100个Point对象,1个Point[]对象,都在GC堆上,Point[]内记录那100个Point的指针(如果把GC句柄理解为指针的话)
    如果是struct,则GC堆上只有一个Point[]对象,里面直接记录对象的内容并且紧密相连,这个和C/C++内的array行为相同实际上在上面例子的情况,应该使用class的性能更高,因为如果用struct的话,需要做100次装箱的操作,而这个过程等于线程堆栈和GC堆上的数据拷贝,很损伤性能
    而如果出现了Point p = points[i]这样的代码,则还要进行100次拆箱的操作
      

  2.   

    对于Class那段程序并没有出现101个Point对象,而是只有100个,剩下的那个对象类型是Point[]而不是Point。前100个Point对象是在托管堆上分配的,那个Point[]类型的对象是在栈空间上分配的。
    这里所说的内联分配我估计它的意思就是指在栈上分配空间。
      

  3.   

    Sunmast不懂别乱讲,上面那段代码明明是struct的运行效率高,你试一下就知道了。using System;internal class CPoint {
    public int x, y; public CPoint(int x, int y) {
    this.x = x;
    this.y = y;
    }
    }internal struct SPoint {
    public int x, y; public SPoint(int x, int y) {
    this.x = x;
    this.y = y;
    }
    }class Test {
    private const int TestCount = 1000000; private static void Main() {
    // Test Class
    long startTime = DateTime.Now.Ticks; CPoint[] cPoints = new CPoint[TestCount];
    for (int i = 0; i < TestCount; i++) {
    cPoints[i] = new CPoint(i, i*i);
    } Console.WriteLine(DateTime.Now.Ticks - startTime); // Test Struct
    startTime = DateTime.Now.Ticks; SPoint[] sPoints = new SPoint[TestCount];
    for (int i = 0; i < TestCount; i++) {
    sPoints[i] = new SPoint(i, i*i);
    } Console.WriteLine(DateTime.Now.Ticks - startTime); Console.ReadLine();
    }
    }
      

  4.   

    只有当值类型向上转型为object时,才会涉及到Boxing(装箱)以及相反转型时的UnBoxing,而这段代码中的new(Test Struct中的)只是调用struct的构造函数而已,并没有什么Boxing操作的。
      

  5.   

    // 那个Point[]类型的对象是在栈空间上分配的
    你错了,.NET内所有的数组都在堆上// 这段代码中的new(Test Struct中的)只是调用struct的构造函数而已
    points[i] = new Point(i, i*i)这样的代码没有装箱操作,但很多情况是,自己写的struct往往都没有构造函数(除了默认的),这样得先在线程上建立值类型对象,做了一些操作之后,再装箱到数组内,这样就会出现我前面讲的问题;而如果先new xxx到数组内,再给各个字段辅值,代价会更高,因为每次辅值都由隐藏的拆箱的过程
    所以你的测试代码并没有完全说明问题而Point p = points[i]这样的代码或者points[i].xxx这样的代码,拆箱的代价会很高
      

  6.   

    什么是内联分配呢
    是不是每个应用程序域都有的哪个内置池呀,可能是实例对象;;;;
    string a;是一个对像
    string a="ttt";也是一个,
    加一起不就是101个嘛,
    结构在栈上,只分配一次内存空间,
    要为类的实例在内存中分配空间,还要为引用这个实例的变量分配。
    可能是这样啊,,上边的都跑题了。和拆箱有什么关系呀
      

  7.   

    // 那个Point[]类型的对象是在栈空间上分配的
    你错了,.NET内所有的数组都在堆上
    A:Point[]怎么可能是分配在椎上的呢?请看下面这段代码。
    Point[] pts;
    pts = new Point[10];
    十个Point在椎上面,而那个pts就在栈上面,你可以用ildasm看一看
      .maxstack  2
      .locals init ([0] valuetype SPoint[] sPoints,
               [1] int32 i)
    i是后面的循环变量。
      

  8.   

    internal struct SPoint {
    public int x, y; public SPoint(int x, int y) {
    this.x = x;
    this.y = y;
    }
    }class Test {
    private const int TestCount = 10; private static void Main() {
    SPoint[] sPoints = new SPoint[TestCount]; for (int i = 0; i < TestCount; i++) {
    // sPoints[i] = new SPoint(i,i);
    sPoints[i] = new SPoint();
    sPoints[i].x = i;
    sPoints[i].y = i;
    } SPoint p = sPoints[7];
    p.x=99;
    Console.WriteLine(sPoints[7].x);//显示7 Console.ReadLine();
    }
    }
    再看看这段代码,SPoint p = sPoints[7];并不是什么UnBoxing操作,你可以用ILDasm看的,里面没有unbox指令的,而是将第八个元素复制给了p,这里是复制而不是引用,因为继承于ValueType的值类型都是实现深Copy的。
      

  9.   

    数组总是在堆上分配内存,这是个显而易见的事实
    如果在栈上分配,那么:
    1、System.Array是值类型,应该继承System.ValueType
    2、将不能创建占用内存超过最大栈空间的数组(比如new byte[1024*1024*10],内存占用达到10MB),据我试验,.NET程序栈空间在1MB左右
    3、把一个Array从一个函数传递到另一个函数,CLR将把Array复制一遍,所以后者对Array的修改不会影响前者
    你可以对这些结论测试.NET Framework程序设计一书也明确指出.NET的数组只能分配在堆上// 里面没有unbox指令的,而是将第八个元素复制给了p
    如果你坚持要把堆和堆栈上复制数据的过程和unbox过程分开,那你是对的
    如果你把值类型赋给一个object,才会使用box指令;再转换回来的时候,才会使用unbox指令
    数组在这里有点特殊,虽然没有用这两个指令,但是线程堆栈和堆上的数据复制确实发生了确实有点跑题 :-) 不过这些都是相关的问题
      

  10.   

    我的意思并不是说数组是完全分配在栈空间上的,而是指数组的元素都是在堆空间上分配的,但指向这个堆空间的指针是在栈空间的。对于楼主这个例子来说,那100个Point类型的对象都是在堆上分配,而那个Point[]类型的引用是在栈空间上分配的。
    就是《.NET Framework程序设计》一书中P310页中左边的那个myIntegers。
    To 楼主:上面这本书P310页这个图就会告诉你什么答案,包括你问的内联。
      

  11.   

    至于那个Box的问题,是我们理解的不一样^_^
    但“数组在这里有点特殊,虽然没有用这两个指令,但是线程堆栈和堆上的数据复制确实发生了”这句话你是如何得到的结论?希望能说明一下。