简单来说,struct是值类型,创建一个struct类型的实例被分配在栈上。class是引用类型,创建一个class类型实例被分配在托管堆上。但struct和class的区别远不止这么简单。

概括来讲,struct和class的不同体现在:

● class是引用类型,struct是值类型,所有他有值类型和引用类型的区别特征,参见《C#值类型和引用类型的区别》 ● class的实例创建是在托管堆上,struct实例创建是在栈上
● class实例的赋值,赋的是引用地址,struct实例的赋值,赋的是值
● class作为参数类型传递,传递的是引用地址,struct作为参数类型传递,传递的是值

● class 的默认访问权限是private,而struct的的默认访问权限public
● struct不能显式地声明无参数构造函数(默认构造函数)或析构函数,也就是struct声明的构造函数必须带有参数;而class可以显式声明无参数构造函数。(由于struct的副本由编译器自动创建和销毁,因此不需要使用默认构造函数和析构函数。实际上,编译器通过为所有字段赋予默认值(参见默认值表(C# 参考))来实现默认构造函数)

● 如果class中只声明了一个有参数的构造函数,则用new关键字创建实例时不能再无参数构造函数(默认构造函数),否则会出现”XXX不包含0个参数的构造函数"的编译错误,这句话的意思表明class中除非没有一个构造函数,否则声明了什么构造函数,就只能用什么构造函数。

而struct中由于只能声明带参数的构造函数,当创建实例时

● class创建实例必须用new关键字,而struct可以用new,也可以不用new,区别在于用new生成的struct中,struct的成员函数是有初始值的。例子如下:

1. struct Point  
2. {  
3. float  x;  
4. float y;  
5. }  
6. Point p = new Point();p是值类型所以应该在栈上为其分配空间  
7. float a = p.x;  //编译通过,使用new 语句,C#结构体中的成员已经得到初始化,a的值为0  
8. Point p;  
9. foalt a = p.x;  //编译不过,编译器认为p的字段未得到初始化,是未知的

● class支持继承,struct不支持继承,但支持接口。

● class偏向于"面向对象",用于复杂、大型数据,struct偏向于"简单值",比如小于16字节,结构简单
● class的成员变量可以在声明的时候赋初值,而在struct声明中,除非字段被声明为 const 或 static,否则无法初始化。

一、从赋值的角度体验struct和class的不同

引用类型赋值,是把地址赋值给了变量

1. class Program  
2.   {  
3. static void Main(string[] args)  
4.     {  
5. new SizeClass(){Width = 10, Length = 10};  
6. "赋值前:width={0},length={1}", sizeClass.Width, sizeClass.Length);  
7.    
8.       var copyOfSizeClass = sizeClass;  
9.       copyOfSizeClass.Length = 5;  
10.       copyOfSizeClass.Width = 5;  
11. "赋值后:width={0},length={1}",sizeClass.Width, sizeClass.Length);  
12.       Console.ReadKey();  
13.     }  
14.   }  
15.    
16. public class SizeClass  
17.   {  
18. public int Width { get; set; }  
19. public int Length { get; set; }  
20.   }  
21.    
22. public struct SizeStruct  
23.   {  
24. public int Width { get; set; }  
25. public int Length { get; set; }  
26.   }

运行结果如下图所示:

以上,当把sizeClass赋值给copyOfSize变量的时候,是把sizeClass所指向的地址赋值给了copyOfSize变量,2个变量同时指向同一个地址。所以,当改变copyOfSizeClass变量的值,也相当于改变了sizeClass的值。

struct类型赋值,是完全拷贝,在栈上多了一个完全一样的变量

1. class Program  
2.   {  
3. static void Main(string[] args)  
4.     {  
5. new SizeStruct(){Length = 10, Width = 10};  
6. "赋值前:width={0},length={1}", sizeStruct.Width, sizeStruct.Length);  
7.    
8.       var copyOfSizeStruct = sizeStruct;  
9.       copyOfSizeStruct.Length = 5;  
10.       copyOfSizeStruct.Width = 5;  
11. "赋值后:width={0},length={1}", sizeStruct.Width, sizeStruct.Length);  
12.       Console.ReadKey();  
13.     }  
14.   }

程序运行结果如下图所示:

以上,当把sizeStruct赋值给copyOfSizeStruct变量的时候,是完全拷贝,改变copyOfSizeStruct的值不会影响到sizeStruct。

二、从参数传值角度体验struct和class的不同

引用类型参数传递的是地址

1. class Program  
2.   {  
3. static void Main(string[] args)  
4.     {  
5. string> temp = new List<string>(){"my","god"};  
6.       ChangeReferenceType(temp);  
7. " "));  
8.       Console.ReadKey();  
9.     }  
10.    
11. public static void ChangeReferenceType(List<string> list)  
12.     {  
13. new List<string>(){"hello", "world"};  
14.     }  
15.   }

运行结果:my god

为什么不是hello world?
→栈上的temp指向托管堆上的一个集合实例
→当temp放到ChangeReferenceType(temp)方法中,本质是把temp指向的地址赋值给了变量list
→在ChangeReferenceType(List<string> list)方法内部,又把变量list的指向了另外一个集合实例地址
→但temp的指向地址一直没有改变

我们再来改变ChangeReferenceType(List<string> list)内部实现方式,其它不变。

    1. class Program  
    2.   {  
    3. static void Main(string[] args)  
    4.     {  
    5. string> temp = new List<string>(){"my","god"};       
    6.       ChangeReferenceType(temp);  
    7. " "));  
    8.       Console.ReadKey();  
    9.     }  
    10.    
    11. public static void ChangeReferenceType(List<string> list)  
    12.     {  
    13.       list.Clear();  
    14. "hello");  
    15. "world");  
    16.     }  
    17.   }

    运行结果:hello world

    为什么不是my god?  
    →栈上的temp指向托管堆上的一个集合实例
    →当temp放到ChangeReferenceType(temp)方法中,本质是把temp指向的地址赋值给了变量list
    →在ChangeReferenceType(List<string> list)方法内部,把temp和list共同指向的实例清空,又添加"hello"和"world"2个元素
    →由于list和temp指向的实例是一样的,所以改变list指向的实例就等同于改变temp指向的实例

    以上,很好地说明了:引用类型参数传递的是地址。

    值类型struct参数传递的是值

      1. class Program  
      2.   {  
      3. static void Main(string[] args)  
      4.     {  
      5. new Size(){Length = 10, Width = 10};  
      6.       ChangeStructType(s);  
      7. "Length={0},Width={1}", s.Length,s.Width);  
      8.       Console.ReadKey();  
      9.     }  
      10.    
      11. public static void ChangeStructType(Size size)  
      12.     {  
      13.       size.Length = 0;  
      14.       size.Width = 0;  
      15.     }  
      16.   }  
      17.    
      18. public struct Size  
      19.   {  
      20. public int Length { get; set; }  
      21. public int Width { get; set; }  
      22.   }

      运行结果如下图所示:

      为什么Length和Width不是0呢?
      →在栈上变量size
      →当通过ChangeStructType(size),把s变量赋值给ChangeStructType(Size size)中的size变量,其本质是在栈上又创建了一个变量size,size的值和s是完全一样的
      →在ChangeStructType(Size size)内部改变size的值,与变量s毫无关系

      三、从struct类型的struct类型属性和struct引用类型属性体验struct和class的不同

      假设有一个struct,它有struct类型的属性

      以下, struct类型Room有struct类型的属性TableSize和TvSize,我们如何通过Room实例来修改其struct类型的属性值呢?

      1. class Program  
      2.   {  
      3. static void Main(string[] args)  
      4.     {  
      5. new Room()  
      6.       {  
      7. new Size(){Length = 100, Width = 80},  
      8. new Size(){Length = 10, Width = 8}  
      9.       };  
      10.    
      11.       r.TableSize.Length = 0;  
      12.          
      13. "table目前的尺寸是:length={0},width={1}", r.TableSize.Length, r.TableSize.Width);  
      14.       Console.ReadKey();  
      15.     }  
      16.   }  
      17.    
      18. public struct Size  
      19.   {  
      20. public int Length { get; set; }  
      21. public int Width { get; set; }  
      22.   }  
      23.    
      24. public struct Room  
      25.   {  
      26. public Size TableSize { get; set; }  
      27. public Size TvSize { get; set; }  
      28.   }

      以上,r.TableSize.Length = 0;此处会报错:不能修改r.TableSize的值,因为不是变量。的确,r.TableSize只是Size的一份拷贝,而且也没有赋值给其它变量,所以r.TableSize是临时的,会被自动回收,对其赋值也是没有意义的。

       如果要修改r.TableSize,只需把

      r.TableSize.Length = 0;

      改成如下:

      r.TableSize =          new          Size(){Length = 0, Width = 0};

      运行结果如下图所示:

      可见,改变struct类型的struct类型属性的某个属性是行不通的,因为像以上r.TableSize只是一份拷贝,是临时的,会被自动回收的。要改变struct类型的struct类型属性,就需要像上面一样,给r.TableSize赋上一个完整的Size实例。

      假设有一个struct,它有引用类型的属性呢?

      以下,struct类型的Room有引用类型属性,TableSize和TvSize,如何通过Room实例来修改其引用类型的属性值呢?并且,我们在类Size中定义了一个事件,当给Size的属性赋值时就触发事件,提示size类的属性值发生了改变。

      1. class Program  
      2.   {  
      3. static void Main(string[] args)  
      4.     {  
      5. new Size() {Length = 10, Width = 10};  
      6.       var twoSize = oneSize;  
      7.    
      8. "Size发生了改变~~");  
      9.       oneSize.Length = 0;  
      10.       Console.ReadKey();  
      11.     }  
      12.   }  
      13.    
      14. public class Size  
      15.   {  
      16. private int _length;  
      17. private int _width;  
      18.    
      19. public event System.EventHandler Changed;  
      20.    
      21. public int Length  
      22.     {  
      23. get { return _length; }  
      24. set  
      25.       {  
      26.         _length = value;  
      27.         OnChanged();  
      28.       }  
      29.     }  
      30.    
      31. public int Width  
      32.     {  
      33. get { return _width; }  
      34. set { _width = value; OnChanged(); }  
      35.     }  
      36.    
      37. private void OnChanged()  
      38.     {  
      39. if (Changed != null)  
      40.       {  
      41. this, new EventArgs());  
      42.       }  
      43.     }  
      44.   }  
      45.    
      46. public struct Room  
      47.   {  
      48. public Size TableSize { get; set; }  
      49. public Size TvSize { get; set; }  
      50.   }

      运行,显示:Size发生了改变~~

      对oneSize.Length的修改,实际上修改的是oneSize.Length指向托管堆上的实例。

      四、从构造函数体验struct和class的不同

      struct类型包含隐式的默认无参构造函数

        1. class Program  
        2.   {  
        3. static void Main(string[] args)  
        4.     {  
        5. new SizeStruct();  
        6. "length={0},width={1}", size.Length, size.Width);  
        7.       Console.ReadKey();  
        8.     }  
        9.   }  
        10.    
        11. public struct SizeStruct  
        12.   {  
        13. public int Length { get; set; }  
        14. public int Width { get; set; }  
        15.   }

        运行结果如下图所示:

        为什么我们没有给SizeStruct定义无参构造函数,而没有报错?
        --因为,struct类型有一个隐式的无参构造函数,并且给所有的成员赋上默认值,int类型属性成员的默认值是0。

        而如果显式声明struct的默认构造函数,则会报错,

          1. public struct SizeStruct  
          2. {  
          3. public SizeStruct() { } //编译错误,提示结构不能包含显式的无参数构造函数  
          4. public int Length { get; set; }  
          5. public int Width { get; set; }  
          6. }

          但是可以声明struct的带参数的构造函数

          1. public struct SizeStruct  
          2. {  
          3. public SizeStruct(int a) { }   
          4. public int Length { get; set; }  
          5. public int Width { get; set; }  
          6. }

          一般情况下,如果类中没有声明任何构造函数,那么系统会默认创建一个无参的构造函数,而当你定义了带参数的构造函数以后,系统不会创建无参构造函数,这时候,如果你还想允许无参构造,就必须显式的声明一个

            1. class Program  
            2.   {  
            3. static void Main(string[] args)  
            4.     {  
            5. new SizeClass();  
            6. "length={0},width={1}", size.Length, size.Width);  
            7.       Console.ReadKey();  
            8.     }  
            9.   }  
            10.    
            11. public class SizeClass  
            12.   {  
            13. public int Length { get; set; }  
            14. public int Width { get; set; }  
            15.    
            16. public SizeClass(int length, int width)  
            17.     {  
            18.       Length = length;  
            19.       Width = Width;  
            20.     }  
            21.   }

            运行,报错:SizeClass不包含0个参数的构造函数

            五、从给类型成员赋初值体验struct和class的不同

            如果直接给字段赋初值。

              1. public struct SizeStruct  
              2. {  
              3. public int _length = 10;  
              4. }

              运行,报错:结构中不能有实例字段初始值设定项。除非定义的变量是static或者const类型的

              如果通过构造函数给字段赋初值。

              1. public struct SizeStruct  
              2.   {  
              3. public int _length;  
              4.    
              5. public SizeStruct()  
              6.     {  
              7.       _length = 10;  
              8.     }  
              9.   }

              运行,报错:结构中不能包含显式无参数构造函数

              可见,给struct类型成员赋初值是不太容易的,而给class成员赋初值,no problem。

              何时使用struct,何时使用class?

              在多数情况下,推荐使用class类,因为无论是类的赋值、作为参数类型传递,还是返回类的实例,实际拷贝的是托管堆上引用地址,也就大概4个字节,这非常有助于性能的提升。

              而作为struct类型,无论是赋值,作为参数类型传递,还是返回struct类型实例,是完全拷贝,会占用栈上的空间。根据Microsoft's Value Type Recommendations,在如下情况下,推荐使用struct:

              ● 小于16个字节
              ● 偏向于值,是简单数据,而不是偏向于"面向对象"
              ● 希望值不可变