C#基础之Equals和Dispose

1.equal()和运算符==的区别

  由于C#中有值类型和引用类型,那么相等也分为值相等和引用相等。先来看一个值类型简单的例子,顺便也写了string类型的比较。

     static void Main(string[] args){int n1 = 1;int n2 = 1;Console.WriteLine(n1==n2);Console.WriteLine(n1.Equals(n2));string str1 = "test";string str2 = "test";Console.WriteLine(str1==str2);Console.WriteLine(str1.Equals(str2));Console.ReadKey();//结果是4个 True}

前2个结果为true我可以理解,但是后2个为true我有点怀疑。一直记得不断有人说string是特殊的引用类型,那这里应该是为str1和str2都分配了内存并str1和str2指向各自的内存,但是结果却是true。我感觉很有可能是因为string类的“特殊”,才发现自己一直都没有理解string类的特殊。百度之后发现原来是.NET做的优化,由于str1和str2内容相同,为了节省内存故让str1和str2指向同一个内存区域,这样就不用为str2开辟空间了。这种技术是字符串驻留技术,当CLR初始化时,会创建一个内部的散列表,键为字符串,值Wie指向堆中字符串的引用,JIT编译方法时,添加str1和“test”到空的散列表里了,str2赋值时会先看散列表里有没有相等的,有的就直接指向相等的值的引用。编程中当把string作为参数传递时不会改变原有string对象的值,因为每次赋值会另外开辟内存,这就是string引用类型的特殊之处。

  对于引用类型,==比较的是两个引用是不是指向同一个内存地址,equal()方法比较的是两个对象指向的内存空间内容是否相同。下面是关于引用类型的代码。

 class Program{static void Main(string[] args){//前面已提过这里str1和str2指向同一个引用,故obj1和obj2表示的地址是相同的string str1 = "test";string str2 = "test";object obj1 = str1;object obj2 = str2;Console.WriteLine(obj1 == obj2);                   //TrueConsole.WriteLine(obj1.Equals(obj2));           //True//这种情况的赋值内存没有做优化,故obj3和obj4表示的是不一样的地址string str3 = new string(new char[] { 't', 'e', 's', 't' });string str4 = new string(new char[] { 't', 'e', 's', 't' });object obj3 = str3;object obj4 = str4;Console.WriteLine(obj3 == obj4);                   //FalseConsole.WriteLine(obj3.Equals(obj4));            //True//当用new开辟新的空间创建对象时,people1和people2肯定是指向了不同的内存地址//而且这是2个不同的对象,我们不能只看它们有相同的方法相同的字段,还要看它们的标识等环境变量,//对于people3和peop4来说则是指向了同一块内存区域People people1 = new People("小方");People people2 = new People("小方");Console.WriteLine(people1 == people2);                   //FalseConsole.WriteLine(people1.Equals(people2));            //FalsePeople people3 = new People("小白");People people4 = people3;Console.WriteLine(people3 == people4);                   //TrueConsole.WriteLine(people3.Equals(people4));            //True
            Console.ReadKey();}}class People{string name = null;public string Name{get { return name; }set { name = value; }}public People(string strName){name = strName;}}

引用类型的变量是存在栈中,但指向的是堆中的地址。==操作符比较的是2个变量的值是否相等,那对于引用类型也就是比较它们表示的地址是否相同。equal表示的是2个变量指向的堆中的内容是否相等。

2.思考:为什么要封装字段

  在上面的例子中,已经形成习惯将字段封装成属性。我发现学习过程中大家总是这么写,我有时候会想一下为什么要这样写,但可能还没有做过实际的项目开发(没有因为不这样写被坑过),所有我一直没有想到最本质的原因。因为经常说为了保护数据的安全性不让直接读和写,可是常常看到属性中是既有set又有get的,那这样还不是直接读和写,这样有什么区别呢?今天既然又发现这个问题,不能还不解决了。查阅了前辈的经验后,我发现有2点说到本质了:

1:安全性,也就是我可以在属性中进行判断,比如age,我可以在set的时候加一个if判断范围在0到150,虽然也可以在外部判断,但这样写是一定不会出错的!

2:当出现修改时,我们可以在属性中进行修改,而不必修改整个程序。举个例子,比如业务需求改变为我们给一个变量赋值,get时不是输出原来的值,而是输出这个值乘以2,如果该字段是public,整个程序用到该字段的地方都要修改,然而如果有属性那就很好办了,只需在get时乘以2就可以了。

3.Dispose()、Close()、Finalize()的区别

  Close()和Dispose()差不多,只是因为Close这个词更加容易理解,所以在Close()方法内部调用了Dispose()方法。Dispose方法在内部是去调用一个virtual的Dispose(bool)函数去释放资源。具有Dispose()方法的类是实现了IDisposable接口的,很多类实现了IDisposable接口,但是只提供Close(),而不对外提供Dispose()。原因是这些类是显示实现接口,这样的话实现类对象将无法调用Dispose(),比如ClassA实现接口IDisposable接口,如果要调用Dispose方法则只能调用((IDisposable)new ClassA()).Dispose()。这样做的目的也就是提供易于理解的Close()。例外有时候调用Close后我们还可以复活对象,而Dispose一旦被调用就会实实在在的释放资源。

  其实.NET最基本的释放资源方法是Finalize和Dispose这2个方法,Finalize是用于释放非托管资源的,Dispose可以释放所有资源,即可释放托管资源,又可以释放非托管资源。我们程序员是无法显示调用Finalize方法的,这个方法当我们使用析构函数时才能被调用,但是程序员并不会知道什么时候会调用析构函数,这将由垃圾回收器控制。只有当垃圾回收器认为对象符合析构的时候才会调用,程序退出时也会调用析构函数。为了提升性能,我们最好不要使用空的析构函数,因为如果类包括析构函数,则Finalize队列中则会创建一个成员选项,GC处理这个队列时如果选项内容为空那只会导致不必要的性能。

  写到这里我心里有2个疑问,上面提到建议我们不要使用空的析构函数,可以理解是因为要提升性能,可是我写析构函数不就是为了调用Finalize方法释放资源吗?还有既然Dispose方法可以释放所有资源,何必还要写一个Finalize方法呢?直到敲了前辈们写的代码才有了答案。

public class People : IDisposable
{//前面我们说了析构函数实际上是重写了 System.Object 中的虚方法 Finalize, 默认情况下,一个类是没有析构函数的,也就是说,对象被垃圾回收时不会被调用Finalize方法 ~People(){// 必须以Dispose(false)方式调用,以false告诉Dispose(bool disposing)函数是垃圾回收器在调用Finalize时调用的 Dispose(false);}// 无法被客户直接调用 // 如果 disposing 是 true, 那么这个方法是被客户直接调用的,那么托管的,和非托管的资源都可以释放 // 如果 disposing 是 false, 那么函数是从垃圾回收器在调用Finalize时调用的,此时会释放托管资源protected virtual void Dispose(bool disposing){// 那么这个方法是被客户直接调用的,那么托管的,和非托管的资源都可以释放 if (disposing){// 释放 托管资源 //......
        }//释放非托管资源 //......// 那么这个方法是被客户直接调用的,告诉垃圾回收器从Finalization队列中清除自己,从而阻止垃圾回收器调用Finalize方法. if (disposing)GC.SuppressFinalize(this);}//可以被客户直接调用 public void Dispose(){Dispose(true);}
}

从上面的例子中可以看出Finalize和Dispose释放了什么资源,而且如果是Dispose方式释放资源后,还会调用GC.SuppressFinalize(this),这个方法会告诉GC没必要再调用析构函数了,因为已经使用Dispose方法释放资源了。这说明析构函数只是作为资源释放的一种补救措施,同样的我们可以在析构函数中写释放非托管资源的代码,最后再调用Finalize方法以保证资源被完全释放,这样就万无一失了!

 声明:本文原创发表于博客园,作者为方小白,如有错误欢迎指出 。本文未经作者许可不许转载,否则视为侵权。

转载于:https://www.cnblogs.com/fangyz/p/5293888.html

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

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

相关文章

mysql 时间chuo格式化_Mysql时间戳与时间格式转换问题汇总

一、时间戳的定义时间戳指格林威治时间1970年01月01日00时00分00秒起至现在的总秒数。二、时间格式转时间戳的方法使用unix_timestamp函数,如下:SELECT UNIX_TIMESTAMP();//返回当前时间戳SELECT UNIX_TIMESTAMP(2017-12-16 17:29:56) AS t; //返回指定时…

文件系统(文件系统目录结构、磁盘分区、虚拟文件系统)、linux内核结构框图

什么是文件系统? 常规认知就是根目录下那些文件,但其实并不是那样。文件系统是操作系统用于明确存储设备(常见的是磁盘,也有基于NAND Flash的固态硬盘)或分区上的文件的方法和数据结构;即在存储设备上组织…

dockerq启动报错(iptables failed: iptables --wait -t nat -A DOCKER -p tcp -d 0/0 --dport 9876 -j DNAT --t

docker启动报错 (iptables failed: iptables --wait -t nat -A DOCKER -p tcp -d 0/0 --dport 9876 -j DNAT --to-destination 172.17.0.2:9876 ! -i docker0: iptables: No chain/target/match by that name. 解决方案:重启docker systemctl restart docker

Linux进程终止命令kill或kill all​笔记

在linux命令下,如果需要终止某个进程,可以使用kill或者killall等命令来实现。终止命令的原理都是向linux内核发送一个系统操作的信号以及某个进程的ID,然后系统内核会根据指定的进程ID进行相应的处理。 kill命令典型的用法:首先使…

linux驱动(驱动编译、字符设备驱动框架、交叉编译树莓派驱动、树莓派驱动本地编译)

什么是驱动: 驱动就是对底层硬件设备的操作进行封装,并向上层提供函数接口。 设备分类: linux系统将设备分为3类:字符设备、块设备、网络设备。 字符设备:指只能一个字节一个字节读写的设备,不能随机读取…

docker启动报错  (iptables failed: iptables --wait -t nat -A DOCKER -p tcp -d 0/0 --dport 9876 -j DNAT --

docker启动报错 : (iptables failed: iptables --wait -t nat -A DOCKER -p tcp -d 0/0 --dport 9876 -j DNAT --to-destination 172.17.0.2:9876 ! -i docker0: iptables: No chain/target/match by that name. 解决方案: systemctl restart docker

第一个Spark程序

1、Java下Spark开发环境搭建(from http://www.cnblogs.com/eczhou/p/5216918.html)1.1、jdk安装安装oracle下的jdk,我安装的是jdk 1.7,安装完新建系统环境变量JAVA_HOME,变量值为“C:\Program Files\Java\jdk1.7.0_79”…

arduino判断是否连接串口_Arduino-串口通信

Serial”系列函数,所以我们要对其有所了解,下面介绍几个常“Serial”函数。1、Serial.begin()—设置串行每秒传输数据的速率(波特率)。在同计算机通讯时,使用下面这些值:300, 1200, 2400, 4800, 9600, 14400, 19200, 28800, 38400…

最详细的docker安装rocketMQ教程来了

RocketMQ是一款分布式、队列模型的消息中间件,是由阿里巴巴设计的,具有以下特点: 支持严格的消息顺序 支持Topic与Queue两种模式 亿级消息堆积能力 比较友好的分布式特性 同时支持Push与Pull方式消费消息 历经多次天猫双十一海量消息考验…

树莓派IO口驱动代码的编写、微机总线地址、物理地址、虚拟地址、BCM2835芯片手册

地址总线: 百度百科解释: 地址总线 (Address Bus;又称:位址总线) 属于一种电脑总线 (一部份),是由CPU 或有DMA 能力的单元,用来沟通这些单元想要存取(读取/写入&#xff…

夺命雷公狗---DEDECMS----26dedecms面包屑导航的实现

我们在很多项目里面都会用到面包屑导航,而dedecms里面也是给我们封装好面包屑导航的了,如下图所示: 在dede里面实现面包屑导航主要用到{dede:field.position/}标签,我们首先来修改下article_movie.htm内容页的模版文件: 我们修改成…

rust油桶用什么打_草莓用什么膨大素好?草莓膨大剂什么时间打?草莓用什么肥料膨大...

农资365公众号,了解更多生根、根腐、重茬、土传、枯黄萎、根烂病、防治根结线虫、微生物菌肥、膨大坐果、抗病增产的防治方法!草莓含有丰富的营养,并且种植效益较高,其种植范围也比较广。草莓种植期间有很多因素影响草莓果实膨大&…

docker安装kafka,超级简单的

简介 kafka是一个分布式消息队列。具有高性能、持久化、多副本备份、横向扩展能力。生产者往队列里写消息,消费者从队列里取消息进行业务逻辑。一般在架构设计中起到解耦、削峰、异步处理的作用。 kafka对外使用topic的概念,生产者往topic里写消息&…

Linux中常见的环境变量笔记

1、变量:BASHBash Shell的全路径比如:echo $BASH2、变量:BASH_VERSIONBash Shell的版本号3、变量:EUID记录当前用户的UID。root用户值为0。4、FUNCNAME在用户函数体内部,记录当前函数体的函数名。5、变量:H…

消防信号二总线有没电压_春晓161#地块人防工程消防电源监控系统的设计与应用...

涂志燕安科瑞电气股份有限公司,上海 嘉定 201801;摘要:本文简述了消防设备电源的组成原理,分析了消防设备电源监控系统在应用中的设计依据和相关规范。通过安科瑞消防设备电源监控系统在春晓161#地块项目的实例介绍,阐…

大学慕课数据结构单元测试——华中科技大学

第一章绪论单元测试 一、单选(2分) 1、​___C__ 是数据的最小单位。 A.信息项 B.数据元素 C.数据项 D.表元素 2、​以下说法不正确的是 ___B___。 A.数据元素是数据的基本单位 B.数据项可由若干个数据元素构成 C.数据可由若干个数据元素构成 D.数据项是不可分割的最小…

gitlab应用

1.git config --global user.email "mybimt.com"  //注册本地环境 2.ssh-keygen -t rsa -C "mybimt.com" //生成本机的key 3.在gitlab加入.ssh中生成的key //gitlab中注册本机 4.git clone gitmy.git …

RocketMQ同步刷盘和异步刷盘

刷盘机制 同步刷盘和异步刷盘 在broker配置文件里修改参数配置是同步还是异步

vim模式下报错E37: No write since last change No write since last change for buffer “ “

报错如下图所示: 网上的解决方法: 文件为只读文件,无法修改。使用命令:w!强制存盘即可在vim模式下,键入以下命令::w!存盘后在使用vim命令检查是否保存,如未保存,编辑后重复以上操作…

Linux中Shell中取消变量和特殊变量的笔记

1、取消变量取消变量也就是将变量从内存中释放出去,可以使用unset 后面加变量名即可,当然函数的释放同样可以采用该方式处理。比如:name"123"echo ${name}输出:123unset nameecho ${name}输出:#取消函数示例…