• NET基础(4):引用类型和值类型


      CLR支持两种类型:引用类型和值类型。虽然FCL的大多数类型都是引用类型,但程序员用的最多的还是引用类型,引用类型总是从托管堆分配,c#的new操作符返回对象内存地址-即指向对象数据的内存地址。使用引用类型必须注意性能问题。首先要认清楚以下4个方面:

    1、内存必须从托管堆分配。

    2、堆上分配的每个对象都有一些额外的成员,这些成员必须初始化。

    3、对象中的其它字节(为字段而设)总是设为零。

    4、从托管堆分配对象时,可能强制执行一次垃圾回收。

      如果所有类型都是引用类型,应用程序的性能将会显著下降。设想每次使用Int32值时都进行一次内存分配,性能会受到多么大的影响,为了提升简单和常用的类型的性能,CLR提供了名为‘值类型’的轻量级类型,值类型的实例一般在线程栈上分配,在代表值类型实例的变量中不包含指向实例的指针。相反,变量中包含了实例本身的字段。由于变量已经包含了实例的字段。因此,值类型的使用缓解了托管堆的压力,并减少了应用程序生存期内的垃圾回收次数。

      文档清楚指出哪些是值类型,哪些是引用类型。在文档中查看类型时,任何成为‘类’的类型都是引用类型。例如:System.Exception类,System.IO.FileStream类以及System.Random类都是引用类型。相反所有的值类型都成为结构或枚举。例如:System.Int32结构、System.Boolean结构、System.Decimal结构、System.TimeSpan结构、System.DayOfWeek枚举等等。

      进一步研究文档,会发现所有的结构都是抽象类型System.ValueType的直接派生类。System.ValueType本身又直接从System.Object派生。根据定义,所有值类型都必须从System.ValueType派生。CLR和所有编程语言都给予枚举特殊待遇。

      虽然不能在定义值类型时为他选择基类型,但如果愿意,值类型可以实现一个或多个接口。除此之外,所有值类型都隐式密封,目的是防止将值类型用作其他引用类型或这类型的基类。例如:无法将Boolean、Char、Int32、Uint64、Single、Double、Decimal等类型来定义任何新类型。

      以下代码演示了引用类型和值类型的区别:

      

    //引用类型(因为是class)
    class SomeRef
    {
         public Int32 x;
    }
    
    //值类型(因为是struct)
    struct SomeVal
    {
        public Int32 x;
    }
    
    static void ValueTypeDemo()
    {
         SomeRef r1=new SomeRef();                 //往堆上分配
         SomeVal v1 = new SomeVal();               //往栈上分配
         
         r1.x = 5;                                                //提领指针
         v1.x = 5;                                               //在栈上修改
         Console.WriteLine(r1.x);                         //显示为5
         Console.WriteLine(v1.x);                        //同样显示为5
         
         SomeRef r2 = r1;                                 //只复用指针(引用)
         SomeVal v2 = v1;                                //在栈上分配并复制成员
         r1.x = 8;                                             //r1.x和r2.x都会修改成新值
         v1.x=9;                                              //v1.x会修改,v2.x不会修改
        
         Console.WriteLine(r1.x);                      //显示8
         Console.WriteLine(r2.x);                      //显示8
         Console.WriteLine(v1.x);                     //显示9
         Console.WriteLine(v2.x);                     //显示5
    }
    

      上述代码中,SomeVal用struct声明,而不是用更常用的class。在C#中,常用struct声明的类型是值类型,用class声明的类型是引用类型。可以看出引用类型和值类型的区别相当大。再代码中使用类型时,必须注意是值类型还是引用类型,因为这会极大的影响在代码中表达自己意图的方式。

      上述代码中有这样一行:

          

    SomeVal v1 = new SomeVal();               //往栈上分配
    

      因为这行代码的写法,似乎要在托管堆上分配一个SomeVal的实例。但c#编译器知道SomeVal是值类型,所以会生成正确的IL代码,在线程栈上分配一个SomeVal的实例。c#还会确保值类型中所有的字段都初始化为零。

         

    SomeVal v1;   //在栈上分配空间
    

      这一行生成的IL代码也会在线程栈上分配实例,并将字段初始化为零。唯一的区别在于,如果使用new操作符,C#会认为实例已经初始化,以下代码更清楚的进行了说明:

    //这两行代码都能够编译通过,因为c#认为v1的字段已经初始化为0
    SomeVal v1 = new SomeVal();
    Int32 a= v1.x;
    
    //这两行代码不能够编译通过,因为c#不认为v1的字段已经初始化为0
    SomeVal v1;
    Int32 a= v1.x;  //error cs0170: 使用了可能未赋值的字段“x”;
    

      设计自己的类型时,要仔细考虑清楚是否应该定义成值类型还是引用类型。值类型有时候能提供更好的性能。具体的说,除非满足一下全部条件,否则不应该声明为值类型。

    1、类型具有基元类型的行为。也就是说是十分简单的类型,没有成员会修改类型的任何实例字段。

    2、类型不需要从其他任何类型继承。

    3、类型也不派生出其它任何类型。

  • 相关阅读:
    ios布局约束
    IOSanimationDidStop
    iosanimationWithKeyPath
    CALayer的分析
    关于集合的小demo
    关于集合越界后 不能使用迭代器遍历的处理方式
    html--day02
    关于LIst Set Map 异常的知识点---我的笔记
    css入门
    html相关标记的含义
  • 原文地址:https://www.cnblogs.com/sunyj/p/5538051.html
Copyright © 2020-2023  润新知