c#1所搭建的核心基础之值类型和引用类型
这个主题很重要,在.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所搭建的核心基础之值类型和引用类型的更多相关文章
- C#面试基础知识点:值类型和引用类型(1)(填坑文)
目录 前言 C#值类型和引用类型 基类(共同点) 值类型继承基类(不同点) 应用类型继承 技术经理的问题 值类型与引用类型都可以用Equals来比较吗? 如何将一个数组a的值赋予数组b然后对b做修改而 ...
- C#基础:值类型、引用类型与ref关键字
在C#中,ref的意思是按引用传递.可以参考C++: int a = 10, b = 20; void swap(int x, int y) { int temp = x; x = y; y = te ...
- C#复习笔记(2)--C#1所搭建的核心基础
通过对C#1所搭建的核心基础的深入了解,可以知道之后的C#版本在C#1的基础上做了很多扩展,而这些扩展都是基于C#搭建的核心基础而来的. 委托 一.编写委托的过程 委托经常和C语言的“函数指针”挂钩. ...
- C#基础知识系列二(值类型和引用类型、可空类型、堆和栈、装箱和拆箱)
前言 之前对几个没什么理解,只是简单的用过可空类型,也是知道怎么用,至于为什么,还真不太清楚,通过整理本文章学到了很多知识,也许对于以后的各种代码优化都有好处. 本文的重点就是:值类型直接存储其值,引 ...
- C#基础--值类型和引用类型
C#中大多数类型都是引用类型,只有个别特殊情况是值类型. 值类型: 枚举(enum) 结构(struct) 基础类型:int, short, char, bool....(string是引用类型) 引 ...
- c#基础系列1---深入理解值类型和引用类型
"大菜":源于自己刚踏入猿途混沌拾起,自我感觉不是一般的菜,因而得名"大菜",于自身共勉. 不知不觉已经踏入坑已10余年之多,对于c#多多少少有一点自己的认识, ...
- [No0000B9]C# 类型基础 值类型和引用类型 及其 对象复制 浅度复制vs深度复制 深入研究2
接上[No0000B5]C# 类型基础 值类型和引用类型 及其 对象判等 深入研究1 对象复制 有的时候,创建一个对象可能会非常耗时,比如对象需要从远程数据库中获取数据来填充,又或者创建对象需要读取硬 ...
- [No0000B5]C# 类型基础 值类型和引用类型 及其 对象判等 深入研究1
引言 本文之初的目的是讲述设计模式中的 Prototype(原型)模式,但是如果想较清楚地弄明白这个模式,需要了解对象克隆(Object Clone),Clone其实也就是对象复制.复制又分为了浅度复 ...
- C#基础篇五值类型和引用类型
using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace P01M ...
随机推荐
- Unbuntu 14.04 下chrome browser bookmark 显示中文乱码解决方案
来源:http://blog.csdn.net/loveaborn/article/details/29579787 网上有人给出这个问题的解决是通过修改文件/etc/fonts/conf.d/49- ...
- 2014 HDU多校弟九场I题 不会DP也能水出来的简单DP题
听了ZWK大大的思路,就立马1A了 思路是这样的: 算最小GPA的时候,首先每个科目分配到69分(不足的话直接输出GPA 2),然后FOR循环下来使REMAIN POINT减少,每个科目的上限加到10 ...
- 登陆整合实现-QQ互联认证(ASP.NET版本)
原文:登陆整合实现-QQ互联认证(ASP.NET版本) 首先 我们创建一个qq.ashx的页面,这个页面会跳转到QQ的请求界面 代码如下: QQSettingConfig qqSettingConfi ...
- GIT分支操作常用命令
切换分支:git checkout name 撤销修改:git checkout -- file 删除文件:git rm file 查看状态:git status 添加记录:git add file ...
- 第2次增加ssh 主机信任脚本
dr-mysql01:/root# cat a1.sh #用户名 uname="$1" #密码 passwd="$2" #执行检测并安装expect模块 ep= ...
- 对xml进行解析
1.要解析的xml文件 <?xml version="1.0" encoding="utf-8"?> <infos> <city ...
- ant—学习记录一
<?xml version="1.0"?> <project name="helloWorld"> <target name=&q ...
- itextsharp生成pdf后的直接打印问题
原文 itextsharp生成pdf后的直接打印问题 小弟这两天用itextsharp生成pdf文档,生成的pdf可以直接保存在指定路径的文件夹下,可是user不想保存,想要点一下button,就可以 ...
- 数学之路-python计算实战(15)-机器视觉-滤波去噪(归一化块滤波)
# -*- coding: utf-8 -*- #code:myhaspl@myhaspl.com #归一化块滤波 import cv2 import numpy as np fn="tes ...
- Oracle 当前时间加减
当我们用 select sysdate+number from dual ;我们得到的是,当前的时间加上number天后的时间.从这里我们也可以看出,使用这种方式进行时间计算的时候,计算的单位是天, ...