基元类型、引用类型和值类型

版权声明:此文章转载自_infocool

原文链接:http://www.infocool.net/kb/CSharp/201609/193828.html

如需转载请联系听云College团队成员小尹 邮箱:yinhy#tingyun.com

基元类型

基元类型:编译器直接支持的数据类型(char、byte、short、uint、long...),它直接映射到FCL(Framework类库)中存在的类型。如int对应的是System.Int32。

基元类型之间支持隐式或者显示转型,支持写成文本常量Console.WriteLine(555.ToString());

checked和unchecked开启和关闭溢出检查

   //关闭溢出检查
   uint invalid = unchecked((uint)-1);
   //vs默认关闭溢出检查
   int number = 1;
   invalid = (uint)(number - 2);
   number = -1;
   //溢出检查
   invalid = checked((uint)number);

全局检查设置:visual studio解决方案右键“属性”选项-先择“生成”选项-点击“高级”按钮-“高级设置”窗口中即可设置;在编写代码的时候可以打开全局检测开关,虽然影响性能,但是可以有效的检测到一些bug,等到发布程序的时候再关闭全局检测。

1.jpg

引用类型和值类型

引用类型

所有称为“类”的类型都是引用类型

内存必须从托管堆中分配

堆上分配的每个对象都有一些额外的成员,这些成员需要初始化

对象中的其他字节总是设置为0

从托管堆中分配一个对象时,可能强制执行GC(垃圾回收)操作

值类型

除了基元类型外,结构和枚举也属于值类型;所有值类型都是派生至抽象类型System.ValueType

值类型的实例在线程栈上分配(即使被被当作字段嵌入到引用类型对象中)

值类型的实例不受垃圾回收器的控制,减少了应用程序的垃圾回收次数

值类型虽然在性能上优于引用类型,但是依然存在一些问题,如装箱和拆箱、无法继承和派生其他类型(可以继承接口)。

将值类型的变量赋值给另一个值类型变量,会执行逐个字段的复制;而对于引用类型来说只需复制内存地址;即,引用类型可以多个变量指向同一个对象,而值类型的变量是自成一体的对象,所以对一个值类型操作不可能影响另一个值类型变量。

值类型的装箱和拆箱

装箱

将值类型转换成一个引用类型,以下是一个装箱内部发生的事情

1.在托管堆中分配好内存。分配的内存是值类型各个字段的所需的内存加上托管堆上的两个额外成员(同步块索引和类型对象指针)所需的内存量。

2.值类型的字段复制到新分配的堆内存。

3.返回对象的地址。这个地址就是对象的引用

    struct Point
    {
        public int x;
        public int y;
    }
    class Program
    {
        static void Main(string[] args)
        {
            Point p;
            p.x = 1;
            p.y = 2;
            object obj = p;
        }
    }

拆箱

拆箱不是装箱的逆过程,它比装箱简单得多了,只是从已装箱的对象中获取它的各个字段的地址;但是完成拆箱之后一般还接着将这些堆中的值复制到线程栈的过程

   p = (Point)obj;

通过ILDasm.exe工具查看IL代码,如果代码中含有box就说明这个部分发生装箱,如果是unbox即为拆箱

2.jpg

未装箱的值类型之所以比引用类型“轻量级”,主要是因为:

它们不需要在托管堆中分配内存

它们没有堆上对象所需的额外成员(类型对象指针和同步块索引)

由于未装箱值类型没有同步块所引,所以不能对他们使用C#的lock,让多个线程同步访问同一实例

对象相等性和同一性

System.Object的虚方法Equals的作用是在两个对象包含相同的值的情况下返回true;但是如果实参引用的对象不同,就会返回false,这实际上是同一性(identity),并非相等性

由于类型可以重写Object的Equals方法,所以比较两个对象的同一性,可以使用Object的ReferenceEquals。

对象哈希码

System.Object提供了虚方法GetHashCode,它获取任意对象的Int32哈希码,如果定义一个类型重写Equals方法,那么还应该重写GetHashCode,这是应为两个相等的对象,必须具有相同的哈希码。

计算类型实例哈希码的几条规则:

算法提供良好的随机分布,是哈希码获得最佳性能

可以调用基类的GetHashCode方法,并包含它的返回值;但是不要调用Object和ValueType的GetHashCode方法,应为它们的算法性能不是高效的

算法至少一个实例字段

算法中使用的字段是不可变的

包含相同值的不同对象应返回相同的哈希码

dynamic基元类型

类型安全的编程语言的优势:

编译时可以检测到对象的类型,避免不必要的错误

生成的代码更小、更快,因为在编译时进行了更对的假设,并在生成的IL和元数据体现出来

为了方便开发人员使用反射或与基本组件通信,C#允许将一个表达式的类型标记为dynamic。

代码使用dynamic表达式/变量来调用一个成员时,编译器会生成特殊的IL代码(payload),在运行时,payload根据当前由dynamic表达式/变量引用的对象的实际类型来决定具体执行的操作。

dynamic和var的区别

用var声明的局部变量只是一种简化语法,它要求编译器根据表达式推断数据的实际类型

var只能用于方法内部的局部变量,而dynamic可以用于方法内部的局部变量、字段和参数

表达式不能转型为var,但是可以转型为dynamic

必须显示初始化var声明的变量,但dynamic声明的变量无需初始化

C#内建的动态求值功能所产生的额外开销是不容忽视的,虽然动态语言简化了语法,但是也要看是否值得,所以dynamic切勿滥用

参考:CLR via C#


想阅读更多技术文章,请访问听云技术博客,访问听云官方网站感受更多应用性能优化魔力。

关于作者

coco秋洁

我爱学习,学习使我快乐

我要评论

评论请先登录,或注册