值类型和引用类型的区别

直白点儿说:值类型就是现金,要用直接用;引用类型是存折,要用还得先去银行取现。

 

值类型包括:内置值类型、用户自定义值类型、和枚举,如 int,float bool 等,以及struct等。

引用类型包括接口类型、用户自定义的类、委托等。如 string 、DateTime、数组等。

值类型是存储在堆栈中,而引用类型是存储在托管堆上,C#程序首先被编译成IL程序,然后在托管执行。值类型直接从堆栈中里面取值,而引用类型必须要先从堆栈里面取出它的地址,再根据这个地址在堆里找到对应的值。

 

1.    值类型的数据存储在内存的栈中;引用类型的数据存储在内存的堆中,而内存单元中只存放堆中对象的地址。

2.     效率: 值类型效率高,不需要地址转换;引用类型效率较低,需要进行地址转换

3.     值类型表示实际数据,引用类型表示指向存储在内存堆中的数据的指针或引用

4.     值类型继承自System.ValueType,引用类型继承自System.Object

5.     栈的内存分配是自动释放;而堆在.NET中会有GC来释放         值类型使用完后立即回收;引用类型使用完后不立即回收,而是交给GC处理回收

6.      值类型的变量直接存放实际的数据,而引用类型的变量存放的则是数据的地址,即对象的引用。
7.类型扩展: 值类型不易扩展,所有值类型都是密封(seal)的,所以无法派生出新的值类型;引用类型具有多态的特性方便扩展。

装箱。那么什么是装箱呢?其实装箱就是值类型到引用类型的转化过程。将一个值类型变量装箱成一个引用类型变量,首先会在托管堆上为新的引用类型变量分配内存空间,然后将值类型变量拷贝到托管堆上新分配的对象内存中,最后返回新分配的对象内存地址。装箱操作是可逆的,所以还有拆箱操作。拆箱操作获取只想对象中包含值类型部分的指针,然后由程序员手动将其对应的值拷贝给值类型变量。接下来我们来看看典型的装箱和拆箱操作。

   1: public static class BoxingAndUnboxing

   2: {

   3:     public static void Demonstration()

   4:     {

   5:         int ageInt = new int();

   6: 

   7:         // Boxing operation.

   8:         object ageObject = ageInt;

   9: 

  10:         //ageObject = null;

  11: 

  12:         // Unboxing operation.

  13:         ageInt = (int)ageObject;

  14: 

  15:         Console.WriteLine(ageInt);

  16:     }

  17: }

在该方法中,我们首先声明了一个值类型变量ageInt,但并未给它赋值,接着声明了一个典型的引用类型变量ageObject,并把ageInt赋给它,这里就进行了一次装箱操作。编译器现在托管堆上分配一块内存空间(空间大小为对象中包含的值类型变量所占空间总和外加一个方法表指针和一个SyncBlockIndex),然后把ageInt拷贝到这个空间中,再返回该空间的引用地址。接下来第13行则是拆箱操作,编译器获取到ageObject对象中值类型变量的指针,然后将其值拷贝给值类型变量。如果你把第10行注释掉的代码打开(这是通俗说法,其实就是取消注释),那么第13行就会抛出System.NullReferenceException异常,要说问什么,这又会牵扯出值类型跟引用类型另一个大的不同。看见了吧,声明ageInt时并没有赋值,如果关掉第10行代码,程序不会报错,最后打印出个0,这说明在声明值类型变量时,如果没有初始化赋值,编译器会自动将其赋值为0,既然值类型没有引用,那么它就不可能为空。引用类型不一样,它可以为空引用,一张过期作废的银行卡是可以存在。而如果将一个空的对象拆箱,编译器上哪儿去找它里面的值类型变量的指针呢?所以这也是拆箱操作需要注意的地方。


 

 

 

### C# 中值类型引用类型的概述 在C#编程语言里,数据类型分为两大类:值类型引用类型值类型直接保存它们的实际数据,而引用类型则存储指向实际对象的引用[^1]。 #### 值类型的特征 - **内存位置**:通常情况下,值类型的数据会被分配到栈(stack)上,这意味着创建速度较快且销毁也迅速。 - **复制行为**:每当传递或赋值一个值类型的变量时,实际上是拷贝了一份完整的数据副本给新的变量。因此修改新变量中的数据不会影响原始变量的内容。 - **不可为空性**:默认状态下,值类型不允许为`null`,除非使用可空(`Nullable`)修饰符来定义该字段可以接受`null`作为合法状态之一。 ```csharp int number = 5; // 下面这条语句会报错因为 int 不允许为 null // int? nullableNumber = null; // 正确写法应加上 ? Console.WriteLine(number); // 输出: 5 ``` #### 引用类型的属性 - **内存管理**:对于引用类型而言,其本身仅包含了一个指针(即地址),真正的对象存在于堆(heap)之中。当声明一个新的引用类型实例化之后,在栈上的部分只记录着通往堆里的具体路径而已。 - **共享特性**:多个不同的引用可能指向同一个对象,所以改变其中一个引用所指向的对象内部成员会影响到通过其它任何相同引用访问的结果。 - **支持 `null`** :不同于值类型,默认情况下所有的引用类型都可以设置成`null`表示未初始化的状态。 ```csharp string strA = "hello"; string strB = strA; strB += " world!"; Console.WriteLine(strA); // 输出: hello Console.WriteLine(strB); // 输出: hello world! ``` 上述代码片段展示了字符串拼接过程中产生的不同结果,尽管最初两个字符串引用相同的常量池条目,但在执行加号运算后产生了全新的字符串并更新了`strB`指向的位置,而原`strA`保持不变[^4]。 #### 性能考量 考虑到性能因素,《CLR via C#》提到过如果开发人员未能充分认识到这两者之间的差异,则可能导致难以察觉的问题以及不必要的资源浪费。例如频繁地将大尺寸结构体当作参数传递可能会带来显著的成本增长;同样地,不当处理大型数组或其他复杂对象也会造成垃圾回收器负担加重等问题。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值