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

news2025/1/19 17:03:21

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

值类型和引用类型,是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 字(可选)

这次封装就没问题了。

总结

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

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

java面试题之mysql篇

1、数据库索引 ​​​​​​​ 索引是对数据库表中一列或多列的值进行排序的一种结构,使用索引可快速访问数据库表中的特定信息。如果想按特定职员的姓来查找他或她,则与在表中搜索所有的行相比,索引有助于更快地获取信息。 索引的一个主要…

H5多用途的产品介绍展示单页HTML5静态网页模板

H5多用途的产品介绍展示单页HTML5静态网页模板 源码介绍:一款H5自适应多用途的产品介绍展示单页HTML静态网页模板,可用于团队官网、产品官网。 下载地址: https://www.changyouzuhao.cn/13534.html

26.java-单元测试xml注解

单元测试&xml&注解 单元测试 单元测试就是针对最小的功能单元编写测试代码,Java程序最小的功能单元是方法,因此,单元测试就是针对 Java 方法的测试,进而检查方法的正确性。 简单理解 : 就是一个测试代码的工具 目前测试…

iPhone数据恢复软件有哪些?11 款 iPhone 数据恢复软件

随着技术的出现,我们对智能手机的依赖程度超出了我们的想象。从保存珍贵的相册、电话簿、日记到重要文件,应有尽有。 但我们也意识到,技术给我们带来的东西也可能被夺走。一次错误的触摸或点击可能会删除手机上的所有数据;您可能…

Linux第66步_linux字符设备驱动_挂载和卸载

1、了解linux中的驱动类型: 1)、字符设备驱动 字符设备是limnux驱动中最基本的一类设备驱动,字符设备就是一个一个字节,按照字节流进行读写操作的设备,读写数据是分先后顺序的。如:GPIO输入输出、UART、I2C、SPI、USB、LCD、音频…

Apache Doris 发展历程、技术特性及云原生时代的未来规划

文章目录 每日一句正能量前言作者介绍Apache Doris 特性极简架构高效自运维高并发场景支持MPP 执行引擎明细与聚合模型的统一便捷数据接入Apache Doris 极速 1.0 时代极速列式内存布局向量化的计算框架Cache 亲和度虚函数调用SIMD 指令集 稳定多源基于云原生向量数据库Milvus 的…

Java学习笔记------继承

继承 Java中提供了一个关键字extends,用这个关键字,我们可以让一个类和另一个类建立继承关系 如图,Student和Teacher类中除了study()和teacher()两个成员函数不同,其他重复了&…

万界星空科技商业开源MES

一、万界星空科技商业开源MES系统概述: 万界星空科技免费MES、开源MES、商业开源MES、市面上最好的开源MES、MES源代码、适合二开的开源MES。 1.万界星空开源MES制造执行系统的Java开源版本。 开源mes系统包括系统管理,车间基础数据管理,计…

ArcgisForJS如何使用ArcGIS Server的缓冲区几何服务?

文章目录 0.引言1.使用geometryService生成缓冲区2.使用geometryEngine生成缓冲区 0.引言 ArcGIS For JS是一款强大的JavaScript库,它提供了许多功能,包括使用ArcGIS Server的缓冲区几何服务。缓冲区几何服务是一种服务,它允许你在地理空间数…

网络原理TCP之“三次握手“

TCP内核中的建立连接 众所周知,TCP是有连接的. 当我们在客户端敲出socket new Socket(serverIp,severPort)时,就在系统内核就在建立连接 真正建立连接是在系统内核中建立的,我们程序员只是调用相关的api. 在此处,我们把TCP的建立连接称为三次握手. 系统在内核建立连接时如上…

广联达Linkworks GetAllData 信息泄露漏洞

免责声明:文章来源互联网收集整理,请勿利用文章内的相关技术从事非法测试,由于传播、利用此文所提供的信息或者工具而造成的任何直接或者间接的后果及损失,均由使用者本人负责,所产生的一切不良后果与文章作者无关。该…

Spring 容器、核心容器总结

目录 创建容器获取 bean容器类层次结构图核心容器总结容器相关bean 相关依赖注入相关 创建容器 方式一: 类路径加载配置文件 ApplicationContext ctx new ClassPathXmlApplicationContext("applicationContext.xml");方式二: 文件路径加载配…

MySQL数据库进阶第四篇(视图/存储过程/触发器)

文章目录 一、视图简单介绍与基础语法二、视图的检查选项三、视图的更新四、视图的作用五、存储过程的概念与特点六、存储过程的 创建,调用,查看,删除七、存储过程 — 系统变量八、存储过程 — 用户定义变量九、存储过程 — 局部变量十、存储…

<网络安全>《51 网络攻防专业课<第十四课 - 华为防火墙的使用(4)>

8 防火墙的防范技术(3) 8.1 IP spoofing攻击防范 攻击介绍 为了获得访问权,或隐藏入侵者的身份信息,入侵者生成带有伪造源地址的报文。 处理方法 检测每个接口流入的IP报文的源地址与目的地址,并对报文的源地址反查路…

C#与VisionPro联合开发——串口通信

串口通信 串口通信是一种常见的数据传输方式,通过串行接口(串口)将数据以串行比特流的形式进行传输。在计算机和外部设备之间,串口通信通常是通过串行通信标准(如RS-232)来实现的。串口通信可以用于连接各…

【HarmonyOS】低代码开发—使用低代码开发服务卡片

DevEco Studio还支持使用低代码开发功能开发服务卡片,目前只支持JS语言,且compileSdkVersion必须为7或以上。 下面以创建一个新的服务卡片为例进行说明。 1.打开一个工程,创建服务卡片,创建方法包括如下两种方式: 选…

vue3前端项目开发,具备纯天然的防止爬虫采集的特征

vue3前端项目开发,具备纯天然的防止爬虫采集的特征!众所周知,网络爬虫可以在网上爬取到一些数据,很多公司,为了自己公司的数据安全, 尤其是web端项目,不希望被爬虫采集。那么,您可以使用vue技术…

[c++] char * 和 std::string

1 char * 和 std::string 的区别 char * 字符串是常量字符串,不能修改;std::string 指向的字符串可以修改 实例代码如下图所示,s1 和 s2 均是常量字符串,字符串常量保存在只读数据区,是只读的,不能写&…

【人工智能高频面试题--基础篇】

🚀 作者 :“码上有前” 🚀 文章简介 :人工智能高频面试题 🚀 欢迎小伙伴们 点赞👍、收藏⭐、留言💬 人工智能高频面试题 1.什么是人工智能?什么是人工智能神经网络?2.解释…

Linux之ACL权限chmod命令

一. chmod命令 chmod命令来自英文词组change mode的缩写,其功能是改变文件或目录权限的命令。默认只有文件的所有者和管理员可以设置文件权限,普通用户只能管理自己文件的权限属性。 设置权限时可以使用数字法,亦可使用字母表达式&#xff0…