C#类型基础

C#数据类型主要分为两类,一种是值类型,一种是引用类型.C#中所有的数据类型都是对象,它们可以有方法,属性。
这里写图片描述

1. 值类型
  • 值类型包括:结构体,枚举,以及简单类型如 int , byte , 简单类型实际上是 BCL基类库类型的别名,比如声明一个int类型,实际上是声明一个 System.Int32 结构类型。short(System.Int16),int(System.Int32),long (System.Int64)。

  • 值类型都”隐式”的继承自System.ValueType。
    这里写图片描述

  • System.ValueType直接派生于System.Object(IL中可查看到,如下图:),即System.ValueType本身是一个类类型,而 不是值类型。其关键在于ValueType重写了Equals()方法,从而对值类型按照实例的值来比较,而不是引用地址来比较。
    这里写图片描述
  • 可以用Type.IsValueType属性来判断一个类型是否为值类型;
1.1) 值类型 - 结构体:
  • C#不支持多重继承,因为结构体已经隐式地继承自ValueType,所以结构不支持继承。
  • 编译器隐式地为结构类型创建无参数的构造函数。在这个构造函数中会对结构成员进行初始化,所有的值类型成员被赋予0或相当于0的值,所有的引用类型被赋予null值。因此,Struct类型不可以自行声明无参数的构造函数!
//分析以下代码,其中: TestStruct test = new TestStruct();
//“=”的左边的变量test ,在线程栈上创建一个TestStruct 类型的变量test ,结构
//的所有成员均未赋值。在进行new TestStruct()之前,将test 压到栈上。
//“=”的右边new TestStruct(),new操作符并不分配内存,也不是创建实例。它仅仅是调
//用了TestStruct结构的默认构造函数,根据构造函数去初始化TestStruct结构的所有字段。
//注意这一点,new操作符不会分配内存,仅仅调用TestStruct结构的默认构造函数去初始
//化TestStruct的所有字段

class Program
    {
        static void Main(string[] args)
        {
            TestStruct test = new TestStruct();
            Console.WriteLine(test.j);
        }
    }
    public struct TestStruct
    {
        public int j=>123;
    }
  • 此处对new结构体和new类通过IL进行比较差别,观察差异
class Program
    {
        static void Main(string[] args)
        {
            TestStruct test = new TestStruct();

            TestClass testClass = new TestClass();

            Console.WriteLine(test.j+ testClass.j);
        }
    }
    public struct TestStruct
    {
        public int j => 123;
    }

    public class TestClass
    {
        public int j => 123;

    }

这里写图片描述

  • 上图中newobj的作用是让GC分配一段内存,并且以这段内存的地址作为对象调用.ctor(构造函数)来初始化这个对象
  • 当我们自己定义一个带参数的结构体构造函数时,new结构体时而不调用定义好的构造函数时,可以观察到此无参数的构造函数(隐式)
2.引用类型
  • 当声明一个引用类型的变量,并使用new操作符创建引用类型实例时,在内存中,该引用类型的变量会被分配到栈,变量保存了位于堆上的引用类型的实例的内存地址。变量本身不包含任何类型所定义的数据。如果仅仅声明一个变量,但不使用new操作符,由于在堆上还没有创建类型的实例,因此,变量值为null,即不指向任何对象
  • 引用类型包括: 类,委托,事件,接口等
  • 一般情况下,当使用new操作符时:首先在应用程序堆上创建一个引用类型对象的实例,并为它分配内存地址,自动传递该实例的引用给构造函数(正因为如此,才可以在构造函数中使用this来访问这个实例),然后调用该类型的构造函数,最后返回该实例的引用(内存地址)),赋值给对应的变量。
3.值类型与引用类型性能考虑

值类型:
引用类型的对象在堆上分配内存,而值类型的对象在堆栈上分配内存。
(如果匿名方法中使用了外部变量(外部方法中声明的局部变量),或者迭代器块中声明了变量,那么这些变量将被提升为隐藏类的字段,因此也将分配在堆上。)

引用类型:
内存是在托管堆上分配的,在分配每一个对象时都会包含一些额外的成员(类型对象指针,同步块索引),这些成员必须初始化,在分配对象时,可能会进行一次垃圾回收操作(如果托管堆上的内存不够分配一次对象时)

综上:值类型运行效率比引用类型效率高(即堆栈运行效率比堆高)。

4.性能损耗(装箱和拆箱)

当你在一个.NET应用程序中定义一个变量时,在RAM中会为其分配一些内存块。这块内存有三样东西:变量的名称、变量的数据类型以及变量的值(对于值类型,栈中的值就是变量的实际值,对于引用类型,实际值则放在堆上,堆中保存的是实际值的那个内存地址(指针)),你的变量究竟会被分配到哪种类型的内存取决于数据类型。

装箱:
将一个值类型转换成等价的引用类型。它的过程分为这样几步:
1)在堆上为新生成的对象实例分配内存。该对象实例包含数据,但它没有名称。
2)将栈上值类型变量的值复制到堆上的对象中。
3)将堆上创建的对象的地址返回给引用类型变量。
无论值类型还是引用类型变量,都是在栈里存了一个值。

拆箱:
拆箱操作需要显示声明拆箱后转换的类型。它分为两步来完成:
1)获取已装箱的对象的地址。
2)将值从堆上的对象中复制到堆栈上的值变量中。
可见,装箱和拆箱需要反复在堆上进行操作,因此,在程序中应该尽量避免无意义的装
箱和拆箱。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值