值类型

老生常谈                                                                                                                                                   

所有的类型可以划分为两类:值类型和引用类型。他们的区别在于复制策略的差异,后者又造成每种类型在内存中的存储位置不同。

值类型

  值类型直接包含值。换句话说,变量引用的位置就是值在内存中实际存储的位置。因此,将一个变量的值赋值给另一个变量时,会在新变量的位置创建原始变量的值的一个内存副本。所以,更改第一个变量的值是不会影响第二个变量的值。将值类型作为参数传递给方法时,也会生成一个内存副本。

  值类型所需要的内存量会在编译时固定下来,且不会在运行时改变。

引用类型

  引用类型存储的是对一个内存地址的引用,要去该位置才能找到真正的数据。因此,要访问数据,“运行时”要从变量中读取内存的位置,然后跳转到包含数据的位置。

  引用类型的访问需要一次额外的跳转,所以速度似乎会慢一些。然而,将一个引用类型的变量赋值给另一个引用类型的变量,只会多出地址的一个内存副本(32位处理器只需要一个32位的地址,64位处理器值需要一个64位的地址),它在内存的利用率上更好一些。在数据较大的情况下,由于不需要复制实际数据,所以引用类型比值类型更有效。

  除了string和object,C#基本类型都是值类型。

一、结构

  定义一个自定义的值类型。

    internal struct Angle
    {
        public Angle(int hours, int minutes, int seconds)
        {
            _hours = hours;
            _minutes = minutes;
            _seconds = seconds;
        }
        public int Hours {
            get { return _hours;}
        }
        private readonly int _hours;
        public int Minutes {
            get { return _minutes; }
        }
        private readonly int _minutes;
        public int Seconds {
            get { return _seconds; }
        }
        private readonly int _seconds;

        public Angle Move(int hours, int minutes, int seconds)
        {
            return new Angle(Hours + hours, Minutes + minutes, Seconds + seconds);
        }
    }
View Code

  虽然语言本身未做要求,但作为一个良好的习惯,我们应该确保值类型是不可变。换句话说,一段实例了一个值类型,那么这个实例就不能修改。如果要修改,应创建一个新的实例(如上面代码Move方法)。

二、装箱

  值类型转换成引用类型的过程叫装箱(boxing),相反的过程称为拆箱(unboxing)。装箱过程:

  1.在堆中分配好内存。将用于存放值类型的数据及少许额外开销(一个SyncBlock-Index以及方法表指针)。

  2.接着发生一次内存复制动作,栈上值类型数据复制到堆上分配好的位置。

  3.最后,引用类型对象引用得到更新,指向堆上的位置。

  不允许在locak()语句中使用值类型

  lock语句:用于同步代码。实际会编译为System.Threading.Monitor的Enter()和Exit()方法。这两个方法必须成对调用。

        Enter()记录由其唯一的引用型参数传递的一个lock,这样,使用相同的引用调用Exit()的时候,就可以释放该lock。使用值类型的问题在于装箱,每次Enter()和Exit()时,都会在堆上创建一个新值。将一个副本的引用和另一个不同副本的引用进行比较,总会返回false。所以无法将Enter()与对应的Exit()钩到一起。

  避免拆箱

    拆箱指令不包括将数据复制回栈的动作。有些语言可以直接访问堆上的值类型,但在C#中,只有当值类型做为引用类型的一个字段访问时才可能这样。

    由于接口是引用类型,所以通过接口访问已装箱的值时,拆箱和复制是可避免的。

    int number=12;

    object o;

    //boxing

    o=number;

    //避免拆箱

    string text=((IFormattable)o).ToString();

    当调用值类型的接口方法时,实例必须是一个变量,因为方法可能会改变值。由于拆箱会生成一个托管地址,“运行时”就会有一个存储位置和一个变量。结果,运行时值传递接口的托管地址,并不需要拆箱操作。

    除此之外,调用一个struct 的ToString()方法(它重写object的ToString()方法),也不需要执行unboxing。在编译时,显然会调用struct重写的ToString()方法,因为所有值类型都是密封的。

三、枚举

  枚举的基础类型不能是char.

  枚举之间的类型兼容性

    C#不支持两个不同枚举数组的直接转型,办法是先转型为一个数组,在转型为第二个枚举。

原文地址:https://www.cnblogs.com/zhangxiaohui/p/4165180.html