.NET中struct与class的区别

在.net中的struct与class有很多相似之处,比如可以直接new,对于成员可以直接XX.field,以至于有不少程序员在用时,将其混在一起,分不清有何区别。这两者有何区别呢?

1.类型不同

我们先来看一段代码

  static void Main(string[] args)
        {
            TypeDemo();           
            Console.ReadLine();
        }

        // Reference type (because of 'class') 
        class SomeClassRef { public Int32 x; }

        // Value type (because of 'struct') 
        struct SomeStructVal { public Int32 x; }

        static void TypeDemo()
        {
            SomeClassRef r1 = new SomeClassRef(); // Allocated in heap 
            SomeStructVal v1 = new SomeStructVal(); // Allocated on stack 
            r1.x = 5; // Pointer dereference 
            v1.x = 5; // Changed on stack 
            Console.WriteLine("r1=" + r1.x.ToString()); // Displays "5" 
            Console.WriteLine("v1=" + v1.x.ToString()); // Also displays "5" 
            // The left side of Figure 5­2 reflects the situation 
            // after the lines above have executed. 
            SomeClassRef r2 = r1; // Copies reference (pointer) only 
            SomeStructVal v2 = v1; // Allocate on stack & copies members 
            r1.x = 8; // Changes r1.x and r2.x 
            v1.x = 9; // Changes v1.x, not v2.x 
            Console.WriteLine("r1=" + r1.x.ToString()); // Displays "8" 
            Console.WriteLine("r2=" + r2.x.ToString()); // Displays "8" 
            Console.WriteLine("v1=" + v1.x.ToString()); // Displays "9" 
            Console.WriteLine("v2=" + v2.x.ToString()); // Displays "5" 
            // The right side of Figure 5­2 reflects the situation 
            // after ALL of the lines above have executed. 
        }
该代码执行结果如下

.NET中struct与class的区别

从代码中,我们可以看到,r1和v1都是new出来的,同时其字段x都赋值为5.所以输出的是r1=5,v1=5。

接着又定义了r2和v2,并将r2赋值为r1,v2赋值为v1。之后,将字段x分别赋值为8和9,接紧接着输出各自的x。结果是r1和r2的x值相同,都变成了后来赋值的8。v1和v2的x值不一样,r1是第一次赋值的5,r2是后面赋值的9.

那引起这种差异的原因是什么呢?我们先用IL来查看一下生成的EXE。

.NET中struct与class的区别

从IL中我们可以看到,SomeClassRef是继承自System.Object,是Object,是引用类型。SomeStructVal是继承自System.ValueType,是值类型。

也就是说class是引用类型,struct是值类型。

引用类型得到的是地址(或者是指针),这样在r2=r1的赋值过程中,其实是将r1的地址赋给了r2,所以r1和r2指向的其实指向的是同一个对象。而对象是存储在heap中。

值类型是直接存在Thread Statck中。在r2=r1的赋值过程中,是直接的内存数据拷贝。

简单的说,引用类型的赋值,始终只有一个对象,一个数据存储空间。值类型的赋值,赋值几次就有几个对象,几个存储空间。可以参看下图。

.NET中struct与class的区别

在ThreadStatck中存储着r1、r2、v1、v2。r1和r2指向的是Managed Heap中的object。而v1和v2始终在Thread Statck中,其字段x是紧接v1或v2之后。

2.函数传参影响不同

我们常见的基本类型如Int32、bool、byte等都是值类型,都具有相同的特性。因为值类型的赋值是数据拷贝,引用类型是引用拷贝,所以两者在作为函数参数传传递时就会有很大的差异。值类型作参数的,其原始值不会受影响,而引用类型作参数的,其原始值会受影响。测试代码如下

  static void Main(string[] args)
        {          
            TestFunc();
            Console.ReadLine();
        }  
static void TestFunc()
        {
            SomeClassRef r = new SomeClassRef();
            r.x = 1;
            SomeStructVal v = new SomeStructVal();
            v.x = 1;

            RefFunc(r);
            ValFunc(v);

            Console.WriteLine(r.x);
            Console.WriteLine(v.x);
        }

        static void RefFunc(SomeClassRef r)
        {
            r.x = 100;
        }

        static void ValFunc(SomeStructVal v)
        {
            v.x = 100;
        }

.NET中struct与class的区别

从结果可以看到,r为class,为引用类型,在经过RefFunc函数后,其字段x的值变成了100。v为struct,为值类型,在经过ValFunc后,其字段x的值仍然为原始值1.

3.继承性不同

class可以继承,struct不可以继承。

.NET中struct与class的区别

可以看到,SomeStructValChild继承SomeStructVal,编译无法通过。提示说无法从密封类型中派生。这么看来,struct被当成了密封类型。其实所有的值类型都是密封(sealed)类型。比如Boolean, Char, Int32, UInt64, Single, Double, Decimal。大家可以参看一下《CLR via C# 第四版》中的一段原文:

.NET中struct与class的区别

有人会说,这里是用class来继承SomeStructVal的,一个是class,一个是struct,两者不一样,当然不能继承。那我们试试struct。

.NET中struct与class的区别

同样编译不能通过,提示说SomeStructVal不是接口。

当然除了继承性这样类的特性外,还有很多类的特性在struct中也是不可以使用的。比如类的虚方法、重载等。

4.GC的触发

从前面的图中,我们可以看到,SomeClassRef是在Managed Heap中开辟了一块空间来存储,而在Managed Headp开辟了空间,就必然会触发垃圾回收(GC)。在Thread Statck中则不会触发GC。

所以如果不断的去new一个SomeClassRef和new一个SomeStructVal,两者的耗时会有很大的差距。可以推测,SomeStructVal的时间肯定会少于SomeClassRef。下面是测试代码

  private const int TIME_MAX = 100000000;

        static void Main(string[] args)
        {          
            TestStruct();
            TestClass();         
            Console.ReadLine();
        }
 
static void TestStruct()
        {
            Stopwatch sw = new Stopwatch();
            sw.Start();
            for (int i = 0; i < TIME_MAX; i++)
            {
                SomeStructVal v1 = new SomeStructVal();
                v1.x = i;
            }
            sw.Stop();
            Console.WriteLine("Struct time:" + sw.ElapsedMilliseconds.ToString());
        }

        static void TestClass()
        {
            Stopwatch sw = new Stopwatch();
            sw.Start();
            for (int i = 0; i < TIME_MAX; i++)
            {
                SomeClassRef r1 = new SomeClassRef();
                r1.x = i;
            }
            sw.Stop();
            Console.WriteLine("Class time:" + sw.ElapsedMilliseconds.ToString());
        }

测试结果见下图

.NET中struct与class的区别

可以看到,class的时间消耗是struct的3倍多。当然这里的耗时还有class在Managed Headp中开辟空间时需要引用户指针(Type object ptr)和同步块索引(Sync block Index)。

5.装箱与拆箱

对于值类型,如果要转换成Object,会有一个装箱(box),再从Object转换成值类型时,会有一个拆箱的操作。而对于引用类型,则没有装箱和拆箱的过程。这也就引出了struct和class的装箱和拆箱的差别。下面是测试代码

 static void BoxAndUnbox_struct()
        {
            ArrayList a = new ArrayList();
            SomeStructVal v; // Allocate a SomeStructVal (not in the heap). 
            for (Int32 i = 0; i < 10; i++)
            {
                v = new SomeStructVal();
                v.x = i; // Initialize the members in the value type. 
                a.Add(v); // Box the value type and add the 
                // reference to the Arraylist. 
            }
            SomeStructVal v2 = (SomeStructVal)a[0];
            Console.WriteLine(v2.x);
        }

        static void BoxAndUnbox_class()
        {
            ArrayList a = new ArrayList();
            SomeClassRef r; // Allocate SomeClassRef in the heap. 
            for (Int32 i = 0; i < 10; i++)
            {
                r = new SomeClassRef();
                r.x = i; // Initialize the members in the value type. 
                a.Add(r); //add the reference to the Arraylist. 
            }
            SomeClassRef r2 = (SomeClassRef)a[0];
            Console.WriteLine(r2.x);          
        }

编译后,使用IL查看EXE

BoxAndUnbox_class函数

.method private hidebysig static void  BoxAndUnbox_class() cil managed
{
  // 代码大小       75 (0x4b)
  .maxstack  2
  .locals init ([0] class [mscorlib]System.Collections.ArrayList a,
           [1] class ClassAndStruct.Program/SomeClassRef r,
           [2] int32 i,
           [3] class ClassAndStruct.Program/SomeClassRef r2,
           [4] bool CS$4$0000)
  IL_0000:  nop
  IL_0001:  newobj     instance void [mscorlib]System.Collections.ArrayList::.ctor()
  IL_0006:  stloc.0
  IL_0007:  ldc.i4.0
  IL_0008:  stloc.2
  IL_0009:  br.s       IL_0026
  IL_000b:  nop
  IL_000c:  newobj     instance void ClassAndStruct.Program/SomeClassRef::.ctor()
  IL_0011:  stloc.1
  IL_0012:  ldloc.1
  IL_0013:  ldloc.2
  IL_0014:  stfld      int32 ClassAndStruct.Program/SomeClassRef::x
  IL_0019:  ldloc.0
  IL_001a:  ldloc.1
  IL_001b:  callvirt   instance int32 [mscorlib]System.Collections.ArrayList::Add(object)
  IL_0020:  pop
  IL_0021:  nop
  IL_0022:  ldloc.2
  IL_0023:  ldc.i4.1
  IL_0024:  add
  IL_0025:  stloc.2
  IL_0026:  ldloc.2
  IL_0027:  ldc.i4.s   10
  IL_0029:  clt
  IL_002b:  stloc.s    CS$4$0000
  IL_002d:  ldloc.s    CS$4$0000
  IL_002f:  brtrue.s   IL_000b
  IL_0031:  ldloc.0
  IL_0032:  ldc.i4.0
  IL_0033:  callvirt   instance object [mscorlib]System.Collections.ArrayList::get_Item(int32)
  IL_0038:  castclass  ClassAndStruct.Program/SomeClassRef
  IL_003d:  stloc.3
  IL_003e:  ldloc.3
  IL_003f:  ldfld      int32 ClassAndStruct.Program/SomeClassRef::x
  IL_0044:  call       void [mscorlib]System.Console::WriteLine(int32)
  IL_0049:  nop
  IL_004a:  ret
} // end of method Program::BoxAndUnbox_class
可以看到,IL代码中没有一个box或unbox的操作,只有pop一类的操作。可以判定并没有进行装箱和拆箱。

再来看BoxAndUnbox_struct函数

.method private hidebysig static void  BoxAndUnbox_struct() cil managed
{
  // 代码大小       84 (0x54)
  .maxstack  2
  .locals init ([0] class [mscorlib]System.Collections.ArrayList a,
           [1] valuetype ClassAndStruct.Program/SomeStructVal v,
           [2] int32 i,
           [3] valuetype ClassAndStruct.Program/SomeStructVal v2,
           [4] bool CS$4$0000)
  IL_0000:  nop
  IL_0001:  newobj     instance void [mscorlib]System.Collections.ArrayList::.ctor()
  IL_0006:  stloc.0
  IL_0007:  ldc.i4.0
  IL_0008:  stloc.2
  IL_0009:  br.s       IL_002e
  IL_000b:  nop
  IL_000c:  ldloca.s   v
  IL_000e:  initobj    ClassAndStruct.Program/SomeStructVal
  IL_0014:  ldloca.s   v
  IL_0016:  ldloc.2
  IL_0017:  stfld      int32 ClassAndStruct.Program/SomeStructVal::x
  IL_001c:  ldloc.0
  IL_001d:  ldloc.1
  IL_001e:  box        ClassAndStruct.Program/SomeStructVal
  IL_0023:  callvirt   instance int32 [mscorlib]System.Collections.ArrayList::Add(object)
  IL_0028:  pop
  IL_0029:  nop
  IL_002a:  ldloc.2
  IL_002b:  ldc.i4.1
  IL_002c:  add
  IL_002d:  stloc.2
  IL_002e:  ldloc.2
  IL_002f:  ldc.i4.s   10
  IL_0031:  clt
  IL_0033:  stloc.s    CS$4$0000
  IL_0035:  ldloc.s    CS$4$0000
  IL_0037:  brtrue.s   IL_000b
  IL_0039:  ldloc.0
  IL_003a:  ldc.i4.0
  IL_003b:  callvirt   instance object [mscorlib]System.Collections.ArrayList::get_Item(int32)
  IL_0040:  unbox.any  ClassAndStruct.Program/SomeStructVal
  IL_0045:  stloc.3
  IL_0046:  ldloca.s   v2
  IL_0048:  ldfld      int32 ClassAndStruct.Program/SomeStructVal::x
  IL_004d:  call       void [mscorlib]System.Console::WriteLine(int32)
  IL_0052:  nop
  IL_0053:  ret
} // end of method Program::BoxAndUnbox_struct
可以看到在ArrayList的add方法时,有一个box的操作,然后再pop进去。

.NET中struct与class的区别

这里是因为ArrayList的Add方法是针对object的,而SomeStructVal是值类型,所以必须装箱成object,然后再add进去。

在装箱时,值类型会依据ThreadStack中的该值所占的控件在Managed Heap中开辟相应的空间,并装数据拷贝进去(头部还会增加Type ojbect Ptr和Sync block Index)。

这在无形中就增加了空间的消耗,所以使用ArrayList等需要将值类型进行装箱的操作,必须加以考量。当然ArrayList一般可以用List<T>来代替。在泛型(<T>)的List中,直接针对的是T类型,所以如果List的泛型T是值类型,将不会有装箱的操作,可以认为是直接在ThreadStack中开辟空间存数据。

由于有了装箱,所以在拿ArrayList的数据时,必然需要拆箱。

.NET中struct与class的区别

需要注意的是,拆箱并不是简单的引用Managed Heap中的数据,而是将该数据拷贝到了Thread Stack中。所以两者已经不是一个对象了。

下面是测试代码

 static void Unbox()
        {
            SomeStructVal v = new SomeStructVal();
            v.x = 1;
            Object o = v;
            SomeStructVal v2 = (SomeStructVal)o;
            v2.x = 11;
            SomeStructVal v3 = (SomeStructVal)o;
            Console.WriteLine("v2.x="+v2.x.ToString());
            Console.WriteLine("o.x="+v3.x.ToString());
        }

结果如下图

.NET中struct与class的区别
下面是IL的代码

.method private hidebysig static void  Unbox() cil managed
{
  // 代码大小       104 (0x68)
  .maxstack  2
  .locals init ([0] valuetype ClassAndStruct.Program/SomeStructVal v,
           [1] object o,
           [2] valuetype ClassAndStruct.Program/SomeStructVal v2,
           [3] valuetype ClassAndStruct.Program/SomeStructVal v3)
  IL_0000:  nop
  IL_0001:  ldloca.s   v
  IL_0003:  initobj    ClassAndStruct.Program/SomeStructVal
  IL_0009:  ldloca.s   v
  IL_000b:  ldc.i4.1
  IL_000c:  stfld      int32 ClassAndStruct.Program/SomeStructVal::x
  IL_0011:  ldloc.0
  IL_0012:  box        ClassAndStruct.Program/SomeStructVal
  IL_0017:  stloc.1
  IL_0018:  ldloc.1
  IL_0019:  unbox.any  ClassAndStruct.Program/SomeStructVal
  IL_001e:  stloc.2
  IL_001f:  ldloca.s   v2
  IL_0021:  ldc.i4.s   11
  IL_0023:  stfld      int32 ClassAndStruct.Program/SomeStructVal::x
  IL_0028:  ldloc.1
  IL_0029:  unbox.any  ClassAndStruct.Program/SomeStructVal
  IL_002e:  stloc.3
  IL_002f:  ldstr      "v2.x="
  IL_0034:  ldloca.s   v2
  IL_0036:  ldflda     int32 ClassAndStruct.Program/SomeStructVal::x
  IL_003b:  call       instance string [mscorlib]System.Int32::ToString()
  IL_0040:  call       string [mscorlib]System.String::Concat(string,
                                                              string)
  IL_0045:  call       void [mscorlib]System.Console::WriteLine(string)
  IL_004a:  nop
  IL_004b:  ldstr      "o.x="
  IL_0050:  ldloca.s   v3
  IL_0052:  ldflda     int32 ClassAndStruct.Program/SomeStructVal::x
  IL_0057:  call       instance string [mscorlib]System.Int32::ToString()
  IL_005c:  call       string [mscorlib]System.String::Concat(string,
                                                              string)
  IL_0061:  call       void [mscorlib]System.Console::WriteLine(string)
  IL_0066:  nop
  IL_0067:  ret
} // end of method Program::Unbox

我们可以看到在  IL_0012:  box    IL_0019:  unbox.any    IL_0029:  unbox.any 三个地方进行了装箱和拆箱的操作。

将v赋值给o时执行了装箱操作,再将o转换成v2时执行了拆箱操作。但对v2.x进行赋值后,并没有影响o的值(o转换成为v3输出)。

综上,我们可以看到struct和class两者有很大的差别,而这差别根本是因为值类型和引用类型的差别。

下面总结了struct使用的一些条件,仅供参考

1.该类型作为基本类型,且其成员都是基本的类型,没有引用类型。并且该类型不会继承其他类型,也不会被其他类型继承。
2.实例占用的字节数很小(一般是小于等于16字节)
3.如果实例的字节数大于16,但不会作为参数在方法中传递,或者从方法中返回时。

转载请注明出处

http://blog.csdn.net/xxdddail/article/details/36862275

.NET中struct与class的区别,布布扣,bubuko.com

.NET中struct与class的区别

上一篇:【web开发学习笔记】Structs2 Action学习笔记(二)


下一篇:phpwind数据同步本地之后板块排版乱