值类型和引用类型详解(C#)

可能你对值类型和引用类型还不太了解。

值类型和引用类型,是c#比较基础,也必须掌握的知识点,但是也不是那么轻易就能掌握,今天跟着我一起来看看吧。

典型类型

首先我们看看这两种不同的类型有哪些比较典型的代表。

典型值类型

int, long, float, double等原始类型中表示数字的类型都是值类型,表示时间的datetime也是值类型,除此之外我们还可以通过关键字struct自定义值类型。

典型引用类型

原始类型中,array, list, dictionary, queue, stack和string都是引用类型,除此之外我们通过关键字class自定义引用类型。

基类

c#中所有的类型都最终继承自 Object ,这是没有疑问的,但是这其中还有些微区别。

值类型基类

对于值类型来说,除了最终继承自Object,还继承自ValueType,继承链如下

添加图片注释,不超过 140 字(可选)

但是请不要误解,这里仅仅指的是值类型天然是ValueType,但是不代表值类型能够这么声明

struct Struct1 : ValueType { }

这样是会引起编译错误的,值类型不能继承任何其他类型,值类型只能实现接口,不能继承自其它类型只有引用类型既可以实现接口也能继承自其它类型。顺便说一下,还有一点比较重要的是,ValueType重写了Object基类的 Equals 方法和 GetHashCode 方法,所以当使用Equals比较两个值类型的时候,系统会比较两个值类型的各个属性是否相等,再返回结果,这就是所谓的相等性 。与此相对,引用类型在使用Equals的时候,会在后台调用object.ReferenceEquals,换言之,引用类型在比较相等性的时候会考虑同一性 。

引用类型基类

对于引用类型就没有那么麻烦,引用类型不会继承自ValueType。引用类型可以继承其他类型。

在内存中的表现

我们都知道,C#将内存分为了两部分,一个是Stack,另外一个是Managed Heap。一般来说,用于函数调用进栈,函数返回出栈,用的是Stack,而当创造一个新的实例时,会根据创建的实例属于值类型还是引用类型决定使用Stack还是Managed Heap。

值类型在内存中

当创建一个值类型对象时,c#会在Stack上面创建一块空间,这块空间就存放这个值类型对象。

int是一个典型的值类型,如下语句

int age = 10;

会存在于内存中的Stack上面。

添加图片注释,不超过 140 字(可选)

如果把值类型的实例赋值给另外一个值类型,那么效果就是复制一个新的值类型实例。

int myAge = age;

添加图片注释,不超过 140 字(可选)

引用类型在内存中

与值类型在内存中的表现不一样,创建一个引用类型的实例,不但会在Stack上面新建一个引用,还会在Heap上面划分出内存以容纳该引用类型实例。用户在使用的时候通过Stack上面的变量间接引用该实例。

class Author { public string Name{get;set;} public int Age{get;set;} } Author author = new Author(){Name="deatharthas", Age= 32};

添加图片注释,不超过 140 字(可选)

注意看和值类型在内存中的区别,引用类型通过Stack上的变量访问位于Heap上面的实例。

在赋值的时候,拷贝的仅仅是Stack上面的变量,新拷贝出来的对象和旧的对象指向的是同一块内存。

Author myAuthor = author;

添加图片注释,不超过 140 字(可选)

这个时候,author和myAuthor指向同一块内存,称为同一性,通过调用

object.ReferenceEquals(myAuthor, author);

可以得到验证。

但可能有细心的朋友会有疑问了,不是说int是值类型,值类型是存在于Stack上面的吗?为什么在author类里面,它会在Heap里面呢?赞一个细心!值类型一般存在于Stack上面,但如果某个值类型包含于引用类型,那么它也会随着那个引用类型存放在Heap上面。

当参数时的行为区别

c#中的参数传递默认都是传值(by value),但是根据所传递对象是值类型还是引用类型,它们的行为还是有所区别,现在我们来看看。

值类型当参数

值类型当参数的时候,传递到函数内部的是一份值类型的拷贝,所以在函数内部修改这个拷贝不会影响原对象。除非我们在传递参数的时候使用了ref或者out。

引用类型当参数

如果参数是引用类型,传递到函数内部的依然是一份拷贝,但是这个拷贝是其在Stack上面的变量的拷贝,就像上面的赋值那个例子。所以这个时候这份拷贝其实和原对象指向同一块内存(指向同一性),修改这个对象可以反映到原对象上面。

谨慎返回引用类型

编程是一项需要谨慎的工作,有时候我们经常会犯一些错误,而这些错误又是那么的不明显以至于不摔坑几次,我们根本察觉不了,考虑下面一个例子。

class People 
{ 
public string Name
 {
 get;
 set;
 }
public int Age
{
 get;
 set;
 } 
private People _Father = null;
public People Father
{
 get
 {
 return _Father;
 }
}
public People(People father)
{
 _Father = father;
}
public void ShowFather()
{
 Console.WriteLine("father's name is " + Father.Name + " and his age is " + Father.Age);
 }
} 
class Program
 {
 static void Main(string[] args) 
{
 People father = new People(null)
 {
 Name = "father", Age = 60 
};
 People son = new People(father);
 son.ShowFather(); Console.ReadLine();
 }
}

添加图片注释,不超过 140 字(可选)

看起来没什么问题,对吧?Father没有提供setter,似乎是安全的。但是我们试试下面的代码。

static void Main(string[] args)
 { People father = new People(null)
 {
 Name = "father", Age = 60 
}; 
People son = new People(father);
 var f = son.Father;
 f.Name="Changed";
 son.ShowFather();
 Console.ReadLine();
 }

添加图片注释,不超过 140 字(可选)

看,发现了什么,外部改变了本来应该被封装所保护的Father属性,封装被破坏了!

稍微一想我们应该能明白这个道理,Father属性返回的拷贝的变量和原Father变量指向同一块实例。要想解决这个问题,我们要么返回一个值类型,要么返回一个全新的对象。修改Father属性如下:

public People Father 
{ 
get 
{ 
return new People(_Father._Father)
 { 
Name = _Father.Name, Age = _Father.Age 
};
 } 
}

再次测试,

添加图片注释,不超过 140 字(可选)

这次封装就没问题了。

总结

我们大概知道了值类型和引用类型的区别,包括它们的行为,在内存的居住方式,以及使用引用类型时可能会遇到的暗坑,希望大家通过阅读这篇文章,能够加深一些对它们的了解,少走一些弯路。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/408692.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

橘子学es原理01之准备工作

es本身是具备很好的使用特性的,我指的是他的部署方面的,至于后期的使用和运维那还是很一眼难尽的。 我们从这一篇开始就着重于es的一些原理性的的一些探讨,当然我们也会有一些操作性的,业务性的会分为多个栏目来写。比如前面我写的…

java面试(并发)

java线程概念,安全? 进程是系统分配资源的最小单元,线程是操作系统调度的最小单位。线程属于进程。 加锁保证安全。1.JVM提供Synchronized关键字,2.jdk提供各种lock锁 实现多线程方式? 1.继承Thread类,…

【奥威-金蝶云星空BI方案】你要的报表,这里都有!

用金蝶云星空来记账,那确实好,但如果让你再去做一份详细的报表呢?自己开发的话,成本大、耗时长,一旦有了新的需求又要一再开发,长此以往将增加使用者使用难度,降低数据分析对运营决策的时效性。…

2024能源动力、机械自动化与航天航空技术国际学术会议(ICEPMAT2024)

2024能源动力、机械自动化与航天航空技术国际学术会议(ICEPMAT2024) 会议简介 能源动力、机械自动化和航空航天技术国际学术会议(ICEPMAT2024)将于2024年在北京举行。会议将探讨能源动力、机械自动化、航空航天技术领域的新研究热点、核心技术和发展趋…

迷你世界之建筑生成球体

local x0,y0,z00,30,0--起点坐标 local dx,dy,dz60,60,60--外切长方体横纵竖长度 local count,all0,dx*dy*dz--计数,总数 local m,k10000,0--单次生成方块数,无用循环值 local x,y,z0,0,0--当前坐标 local demath.random(2,19)/2 local id600--方块…

在openEuler中通过KVM可视化安装华为FusionCompute的CNA主机

一、环境说明 在Windows物理主机上通过VMware WorkStation创建一个虚拟机(4U4C、16GB内存,400GB磁盘,NAT网络连接),在虚拟机中安装openEuler 22.03 LTS系统,并将该虚拟机作为部署 FusionCompute的服务器&a…

【Linux】 yum命令使用

yum命令 yum( Yellow dog Updater, Modified) 是一个在 Fedora、CentOS 及其它一些基于 RPM 的 Linux 发行版中使用的包管理器。它允许用户自动安装、更新、配置和删除软件包。yum 由 Python 写成,基于 RPM(Red Hat Package Mana…

【C语言】linux内核ipoib模块 - ipoib_tx_poll

一、中文注释 这段代码是 Linux 内核网络栈中与 InfiniBand 协议相关的一个部分,特别是与 IP over InfiniBand (IPoIB)相关。该函数负责去处理IPoIB的发送完成队列(发送CQ)上的工作请求(work completions)。以下是对这…

微信小程序开发(实战案例):本地生活 - 列表页面开发(动态渲染处理)、节流防抖(节流阀应用)

文章目录 本地生活 - 列表页面开发一、将九宫格分类换成navigator组件二、动态设置商品列表页的 title三、动态渲染商品列表页面四、上拉触底加载数据五、添加Loading加载效果六、数据加载节流防抖处理 本地生活 - 列表页面开发 导入我们上次写的 本地生活 - 首页开发的项目 运…

MySQL数据库调优之关联查询、排序查询、分页查询、子查询、Group by优化

关联查询优化 1.准备工作 CREATE TABLE IF NOT EXISTS type(id INT(10) UNSIGNED NOT NULL AUTO_INCREMENT,card INT(10) UNSIGNED NOT NULL,PRIMARY KEY(id));CREATE TABLE IF NOT EXISTS book( bookid INT(10) UNSIGNED NOT NULL AUTO_INCREMENT, card INT(10) UNSIGNED N…

李宏毅2023机器学习作业1--homework1——python语法

# 定义list del_col del_col [0, 38, 39, 46, 51, 56, 57, 64, 69, 74, 75, 82, 87] # 删除raw_x_train中del_col的列,axis为1代表删除列 raw_x_train np.delete(raw_x_train, del_col, axis1) # numpy数组增删查改方法 # 定义列表get_col get_col [35, 36, 37,…

openssl3.2 - 编译 - zlib.dll不要使用绝对路径

文章目录 openssl3.2 - 编译 - 编译时的动态库zlib.dll不要使用绝对路径概述测试zlib特性在安装好的目录中是否正常笔记70-test_tls13certcomp.t80-test_cms.t对测试环境的猜测从头再编译测试安装一次测试一下随便改变位置的openssl用到zlib时是否好使测试一下随便改变位置的op…

【爬虫逆向实战篇】定位加密参数、断点调试与JS代码分析

文章目录 1. 写在前面2. 确认加密参数3. 加密参数定位4. XHR断点调试 【作者主页】:吴秋霖 【作者介绍】:Python领域优质创作者、阿里云博客专家、华为云享专家。长期致力于Python与爬虫领域研究与开发工作! 【作者推荐】:对JS逆向…

实战一个 Jenkins 构建 CI/CD流水线 的简单配置过程哈

引言:上一期我们讲述了gitlabCI/CD工具的介绍,工具之争,本期我们介绍Jenkins CI/CD 目录 一、Jenkins介绍 1、Jenkins概念 2、Jenkins目的 3、特性 4、产品发布流程 二、安装Jenkins 1、安装JDK 2、安装Jenkins 1、上传压缩包 2、…

(done) 如何判断一个矩阵是否可逆?

参考视频:https://www.bilibili.com/video/BV15H4y1y737/?spm_id_from333.337.search-card.all.click&vd_source7a1a0bc74158c6993c7355c5490fc600 这个视频里还暗含了一些引理 1.若 AX XB 且 X 和 A,B 同阶可逆,那么 A 和 B 相似。原因&#xff1…

北航复试知识点总结

2024.2.25 住行 报道+机试+两天面试=4天 面试流程 (每个人大概20min,早一点到考场!) 形式:5位老师(一记录,四提问) 老师 陆峰 办公地址:北京航空航天大学新主楼H1033 电子邮箱: lufeng@buaa.edu.cn 个人主页:http://shi.buaa.edu.cn/lufeng/ 面试礼仪 于无形中…

编曲学习:高叠和弦 挂留和弦 和弦实战应用

高叠和弦 挂留和弦 和弦实战应用小鹅通-专注内容付费的技术服务商https://app8epdhy0u9502.pc.xiaoe-tech.com/live_pc/l_65d4826fe4b04c10a1310517?course_id=course_2XLKtQnQx9GrQHac7OPmHD9tqbv 七和弦 以三和弦举例,三和弦上面叠一个三度的音,就变成了七和弦。 从下到…

Spring Boot利用Kaptcha生成验证码

生成验证码 我们在登录或注册某个网站的时候,会需要我们输入验证码,才能登录注册,那么如何生成验证码呢?其实,生成验证码我们可以用Java Swing在后台内存里的区域画一个出来,但是非常麻烦,所以…

WIFI EEPROM 简略分析-MT7628 EEPROM ANALYSIS

经常做WIFI的同学都了解,硬件设计完成后经过射频的测试满足设计要求后,在量产以及生产中都需要对WIFI的射频需要校准,保证产品射频输出功率的一致性。 在开发阶段就必须需要了解WIFI的EEPROM都是一些什么参数,一般在硬件开发阶段会拿到芯片厂家都SDK,里面都包含对EEPROM的…

用什么软件制作电子杂志

想要制作高大上的电子杂志?别再烦恼啦!今天给大家推荐一款超级实用的软件,让你轻松制作出专业水准的电子杂志! 这款软件功能强大,操作简单,适合所有对设计感兴趣的小伙伴们。无论是新手还是专业设计师&…