这个主题很重要,在.NET中做的一切其实都是在和一个值类型或者引用类型打交道。

现实世界中的值和引用

假定你在读一份非常棒的东西,希望一个朋友也去读他。于是你到复印室里复印了一份。这个时候他获得了属于他自己的一份完整副本。在这种情况下,我们处理的是值类型的行为。你和你朋友是各自独立的。你可以在自己的上面加一些注释,他的报纸不会改变。

再假定你在qq空间里发表了一篇日志,你想让朋友看到,这一次,你唯一需要给你朋友的是你qq空间日志所在的url地址。这就是引用类型的行为。当你修改你的日志时,你的朋友就你那个看到改变。

在c#和.net中,值类型和引用类型的差异与现实世界中差别类似。.Net中的大多数类型都是引用类型。除了一下总结的特殊情况,类(使用class来声明)是引用类型,而结构(使用struct来声明)是值类型。特殊情况包括如下方面:

  • 数组类型时引用类型,即使元素类型是值类型(所以int[]仍是引用类型,即使int是值类型)
  • 枚举(使用enum声明)是值类型
  • 委托类型(使用delegate声明)是引用类型
  • 接口类型(使用interface声明)是引用类型,但是可由值类型实现

值类型和引用类型基础知识

学习值类型和引用类型时,要掌握的重要概念是一个特殊表达式的值时什么。为使问题具体化,使用表达式最常见的例子-变量。但是,同样的道理也适用于属性、方法调用、所引器和其他表达式。

大多数表达式都有与其相关的静态类型。对于值类型的表达式,它的值就是表达式的值,例如“2+3”的值就是5。  然而,引用类型的表达式,他的值是一个引用,而不是该引用所指代的一个对象。所以string.Empty的值不是一个空字符串-而是对空字符串的一个引用。在平常的讨论中,甚至在一些专业文档中,经常混淆这一区别。例如,你可能这样描述: string.Concat的作用是返回一个字符串,该字符串将所有参数都连接到一起。

变量的值是在它声明时的位置存储的。局部变量的值总是存储在栈中。实例变量的值总是存储到实例本身存储的地方。引用实例(对象)总是存储在堆中,静态变量也是。

两种类型的另一个差异在于,值类型不可以派生出其他类型。这将导致的一个结果就是,值不需要额外的信息来描述值实际是什么类型。把他同引用类型比较,对于引用类型来说,每个对象的开头都包含一个数据块,它标识了对象的实际类型,同时还提供了其他一些信息。永远都不能改变对象的类型—执行简单强制转换时,运行时会获取一个引用,检查它引用的对象是不是目标类型的一个有效的对象,如果有效就返回原始引用,否则抛出异常。

走出误区

误区1:“结构是轻量级的类”

这个误区存在多种形式。

有人认为值类型不能或者不应该有方法或其他有意义的行为。对于这种说法,一个非常典型的反例就是DateTime类型:它作为值类型来提供是很有道理的,因为他非常适合作为数字或者字符形似的一个基本单位来使用。另外它也应该赋予对它的值执行计算的能力。换个角度看这个问题,是数据转移类型一般都是引用类型。总之,具体应该如何决定,应取决于需要的是值类型的语义还是引用类型的语义,而不是取决于这个类型简单与否。

还有一些人认为值类型之所以显得比较轻,是因为性能。事实是在某些情况下值类型很能干,它们不需要垃圾回收,不会因引用类型标识而产生开销,也不需要取值这一步操作。但是在其他方面,引用类型更能干,在传递参数、赋值、将值返回和执行类似的操作时,只需复制4或8字节,而不是复制全部数据。假定ArrayList是一个所谓“纯的”值类型,那么将一个ArrayList传递给一个方法时,就得复制它的所有数据!几乎在所有情况下,性能问题都不是根据这种判断决定的。瓶颈从来不是想当然的,在你根据性能进行设计之前,需要衡量不同的选择。

值得注意的是,将这两者结合也不能解决问题:类型(不管是类还是结构)拥有多少方法并不重要,每个实例所占用的内存不会受到影响(代码本身消耗内存,但这只会发生一次,而不是每个实例都发生)

误区二“引用类型在堆上,值类型在栈上”

这个误区主要归咎于转述这句话的人没有动脑筋。第一部分是正确的—引用类型的实例总是在堆上创建的。但第二部分就有问题了。前面讲过,变量的值是在他生明的位置存储的。

所有假定一个类中有一个int类型的实例变量,那么在这个类的任何对象中,该变量的值总是和对象中的其他数据在一起,也就是在堆上。只有局部变量(方法内部声明的变量)和方法参数在栈上。但是在c#2及更高版本,很多局部变量并不完全放在栈上。

误区三:对象在c#中是通过引用传递的

这或许是传播最广的一个误区了。同样,说这句话的人一般(并不总是)知道c#实际的行为是什么,但不知道“引用传递”的真正意义是什么。重要的一点是:假如以引用传递的方式来传送一个变量,那么调用的方法通过更改其参数值,来改变调用者的变量值。现在请记住:引用类型变量是值的引用,而不是对象本身。不需要引用来传递参数本身,就可以更改改参数引用的那个对象的内容。

例子:

using System;

using System.Collections.Generic;

using System.Linq; using System.Text;

using System.Threading.Tasks;

namespace ConsoleApplication1 {

class Program     {

public void AppendHello(StringBuilder builder)         {

builder.Append("hello");

builder = new StringBuilder("xyz");

}

static void Main(string[] args)  {

StringBuilder mybuilder = new StringBuilder("123");

new Program().AppendHello(mybuilder);

Console.Write(mybuilder);

}

}

}

运行结果是123hello,说明参数对象没有发生改变。说明参数不是引用

调用这个方法时,参数值(对某StringBuilder的一个引用)是以值传递(pass by value)的方式传递的。如果想再方法内部更改builder变量的值,如执行builder=null,语句,调用者看不到这一改变。

有趣的是,在这种错误的说法中,不仅“引用传递”的说法有误,而且“对象传递”的说法也存在问题。无论是引用传递还是值传递,对象本身永远不会被传递。涉及一个引用类型时,要么以“引用传递”方式传递变量,要么以“传值”的方式传递参数值。最起码,这回答了当null做为一个传值参数的值来使用时会发生什么的问题。假如传递的是对象,这个时候就会出问题,因为没有一个对象可供传递!相反,null引用会采用和其他引用一样的“值传递”的方式传递。

装箱和拆箱

有时候我们不想用值类型的值,就是想用一个引用。之所以会发生这种情况,有多种原因,幸好,c#和.Net提供了一个名为装箱的机制,它允许根据值类型来创建一个对象,然后使用这个对象的一个引用。在接触实际的例子之前,先来回顾两个重要的事实:

  • 对于引用类型的变量,他的值永远是一个引用
  • 对于值类型的变量,他的值永远是该值类型的一个值

基于这两个事实,下面3行代码第一眼看上去似乎没有太多道理:

int i=5;

object o=i;

int j=(int) o;

这里有两个变量:i是值类型的变量,o是引用类型的变量。将i的值赋给o有道理吗?o的值必须是一个引用,而数字5不是引用,它是一个整数值。实际发生的事情就是装箱:运行时将咋堆上创建一个包含值(5)的对象(它是一个普通对象)。o的值是对该新对象的一个引用。该对象的值时原始值的一个副本,改变i的值不会改变箱内的值。

第三行执行相反的操作:拆箱。必须告诉编译器将object拆箱成什么类型。如果使用了错误的类型,就会抛出一个InvalidCastException异常。同样,拆箱也会复制箱内的值,在赋值以后,j和该对象之间不再有任何联系。

上面这一段话其实已经简单明了地解释了装箱和拆箱。剩下的唯一的问题就是要知道装箱和拆箱在什么时候发生。拆箱一般是很明显的,因为要在代码中明确地显示一个强制类型转换。装箱则可能在没有意识的时候发生。上例展示的是一个简单的版本。但是,为一个类型的值调用ToString,Equals或者GetHashCode方法时,如果该类型没有覆盖这些方法,也会发生装箱。另外,将值作为接口表达式使用时—把它赋给一个接口类型的变量,或者把它作为接口类型的参数来传递也会发生装箱。例如,IComparable x=5;语句会对数字5进行装箱。

之所以要留意装箱和拆箱,是由于它们可能会降低性能。一次装箱或拆箱是微不足道的,但,假如执行千百次这样的操作,那么不仅会增大程序本身的操作开销,还会创建数量众多的对象,而这些对象会加重垃圾回收器的负担。同样,这种性能损失通常也不是大问题,但还是应该引起注意。

整理自:c# in depth

c#1所搭建的核心基础之值类型和引用类型的更多相关文章

  1. C#面试基础知识点:值类型和引用类型(1)(填坑文)

    目录 前言 C#值类型和引用类型 基类(共同点) 值类型继承基类(不同点) 应用类型继承 技术经理的问题 值类型与引用类型都可以用Equals来比较吗? 如何将一个数组a的值赋予数组b然后对b做修改而 ...

  2. C#基础:值类型、引用类型与ref关键字

    在C#中,ref的意思是按引用传递.可以参考C++: int a = 10, b = 20; void swap(int x, int y) { int temp = x; x = y; y = te ...

  3. C#复习笔记(2)--C#1所搭建的核心基础

    通过对C#1所搭建的核心基础的深入了解,可以知道之后的C#版本在C#1的基础上做了很多扩展,而这些扩展都是基于C#搭建的核心基础而来的. 委托 一.编写委托的过程 委托经常和C语言的“函数指针”挂钩. ...

  4. C#基础知识系列二(值类型和引用类型、可空类型、堆和栈、装箱和拆箱)

    前言 之前对几个没什么理解,只是简单的用过可空类型,也是知道怎么用,至于为什么,还真不太清楚,通过整理本文章学到了很多知识,也许对于以后的各种代码优化都有好处. 本文的重点就是:值类型直接存储其值,引 ...

  5. C#基础--值类型和引用类型

    C#中大多数类型都是引用类型,只有个别特殊情况是值类型. 值类型: 枚举(enum) 结构(struct) 基础类型:int, short, char, bool....(string是引用类型) 引 ...

  6. c#基础系列1---深入理解值类型和引用类型

    "大菜":源于自己刚踏入猿途混沌拾起,自我感觉不是一般的菜,因而得名"大菜",于自身共勉. 不知不觉已经踏入坑已10余年之多,对于c#多多少少有一点自己的认识, ...

  7. [No0000B9]C# 类型基础 值类型和引用类型 及其 对象复制 浅度复制vs深度复制 深入研究2

    接上[No0000B5]C# 类型基础 值类型和引用类型 及其 对象判等 深入研究1 对象复制 有的时候,创建一个对象可能会非常耗时,比如对象需要从远程数据库中获取数据来填充,又或者创建对象需要读取硬 ...

  8. [No0000B5]C# 类型基础 值类型和引用类型 及其 对象判等 深入研究1

    引言 本文之初的目的是讲述设计模式中的 Prototype(原型)模式,但是如果想较清楚地弄明白这个模式,需要了解对象克隆(Object Clone),Clone其实也就是对象复制.复制又分为了浅度复 ...

  9. C#基础篇五值类型和引用类型

    using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace P01M ...

随机推荐

  1. php下正则表达式整理

    一.正则表达式的历史背景 1,内容深厚的正则表达式 ^.+@.+\\..+$ 形式 字符串搜索与匹配的工具 2,应用范围 手机输入法 Windows文件搜索 linux 列出文件命令 网站用户注册,如 ...

  2. Python 2.7 学习笔记 元组的使用

    一.元组 python中的元组和列表非常类似,核心区别是元组的内容初始化后是不可以修改的,而队列可以. 关于列表的详细介绍,可查看上一篇列表使用文章. 大部分场景下,能用元组的地方,都可以用列表.但有 ...

  3. 17.1.1 How to Set Up Replication

    17.1.1 How to Set Up Replication 17.1.1.1 Setting the Replication Master Configuration 17.1.1.2 Sett ...

  4. myeclipse自动生成注释

    myeclipse自动生成注释 在使用Eclipse编写Java代码时,自动生成的注释信息都是按照预先设置好的格式生成的,例如其中author的属性值. 我们可以在Eclipse中进行设置自己希望显示 ...

  5. GCD其他实用场景

    GCD线程间通信 dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);     ...

  6. 修改linux文件权限命令:chmod 【转载】

    Linux系统中的每个文件和目录都有访问许可权限,用它来确定谁可以通过何种方式对文件和目录进行访问和操作. chmod  命令可以改变所有子目录的权限,下面有2种方法 改变一个文件的权限: chmod ...

  7. pyfits例子

    下面是一个读入fits文件,画积分强度图,再把某个星表里的天体画到图上的python程序. ====================================================== ...

  8. C-KMP

    一.BF算法 --传统算法 BF算法是普通的模式匹配算法,BF算法的思想就是将目标串S的第一个字符与模式串P的第一个字符进行匹配,若相等,则继续比较S的第二个字符和P的第二个字符:若不相等,则比较S的 ...

  9. 超级坑人的Couchbase数据库问题!!!

    官网:http://www.couchbase.com/ 版本:1.8版 问题描述: 某次服务器因意外断电重启后,就进入不了Couchbase控制台,显示 "无法显示该页" 的错误 ...

  10. P65

    #include<stdio.h> #define N 6 main() { char c[N]; int i=0; for(;i<N;c[i]=getchar(),i++); pr ...