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

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

     static void Main(string[] args)
{
int n1 = ;
int n2 = ;
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); //True
Console.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); //False
Console.WriteLine(obj3.Equals(obj4));   //True //当用new开辟新的空间创建对象时,people1和people2肯定是指向了不同的内存地址
//而且这是2个不同的对象,我们不能只看它们有相同的方法相同的字段,还要看它们的标识等环境变量,
//对于people3和peop4来说则是指向了同一块内存区域
People people1 = new People("小方");
People people2 = new People("小方");
Console.WriteLine(people1 == people2); //False
Console.WriteLine(people1.Equals(people2));   //False
People people3 = new People("小白");
People people4 = people3;
Console.WriteLine(people3 == people4); //True
Console.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方法以保证资源被完全释放,这样就万无一失了!

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

C#基础之Equals和Dispose的更多相关文章

  1. Java基础系列-equals方法和hashCode方法

    原创文章,转载请标注出处:<Java基础系列-equals方法和hashCode方法> 概述         equals方法和hashCode方法都是有Object类定义的. publi ...

  2. 程序猿的日常——Java基础之equals与hashCode

    equals和hashCode是我们日常开发最常使用的方法,但是因为一般都使用默认的规则,因此也很少会引起关注.不过了解他们的用途和设计的原则,还是会帮助我们更好的设计代码. equals equal ...

  3. JAVA基础--toString, equals方法

    ==比较的是地址 equals比较的是内容. 所以要重写object的equals方法. public class TestEquals { public static void main(Strin ...

  4. Java基础之equals方法和"= ="的区别

    ==操作符专门用来比较两个变量的值是否相等,也就是用于比较变量所对应的内存中所存储的数值是否相同,要比较两个基本类型的数据或两个引用变量是否相等,只能用==操作符. 如果一个变量指向的数据是对象类型的 ...

  5. Java基础之equals() 和 hashCode()

    equals()是Object中的一个方法: public boolean equals(Object obj) { return (this == obj); } 在Object中equals()方 ...

  6. java基础总结--equals与==

    equals 与 == 先上一段经典代码 public static void main(String[] args) { // TODO Auto-generated method stub int ...

  7. Java基础系列 - equals和==的区别

    package com.test7; public class test7 { public static void main(String[] args) { /** * 1.equals()比较的 ...

  8. 十四、Java基础---------String、StringBuffer、StringBuilder基本应用

    在前面的博客中曾提及Java的数据类型分为基本数据类型,和引用数据类型,而String便是最常见的应用数据类型,本文将着重介绍这一引用数据类型的用法. String 字符串     String类是对 ...

  9. 实现 Dispose 方法

    实现 Dispose 方法 MSDN 类型的 Dispose 方法应释放它拥有的所有资源.它还应该通过调用其父类型的 Dispose 方法释放其基类型拥有的所有资源.该父类型的 Dispose 方法应 ...

随机推荐

  1. Ubuntu root 密码忘记-恢复

    @Ubuntu root 密码忘记-恢复 2012-04-27 11:09:22 方法一: 如果用户具有sudo权限,那么直接可以运行如下命令: #sudo su root #passwd #更改密码 ...

  2. BZOJ3697:采药人的路径(点分治)

    Description 采药人的药田是一个树状结构,每条路径上都种植着同种药材. 采药人以自己对药材独到的见解,对每种药材进行了分类.大致分为两类,一种是阴性的,一种是阳性的. 采药人每天都要进行采药 ...

  3. 随手练——洛谷-P1002 过河卒(动态规划入门)

    题目链接:https://www.luogu.org/problemnew/show/P1002 题目还算良心,提醒了结果可能很大,确实爆了int范围, 这是一开始写的版本,用递归做的,先给地图做标记 ...

  4. Linux环境搭建多项目SVN

    1.安装SVN #yum install subversion 2.创建版本库文件夹 #mkdir -p /var/svn/repos/pro1 (/var/svn/repos是根路径,pro1是项目 ...

  5. deque详解

    deque是double-ended queue的简称,deque和vector几乎上是一样的,使用的非常少,定义在<deque>头文件里: deque和vector的区别在于: 1)de ...

  6. ApiCloud模块链接

    城市选择器 页面左右滑动 识别信用卡 图像coverFlow  输入框  图片浏览器  百度地图                                                     ...

  7. Codeforce Round #554 Div.2 D - Neko and Aki's Prank

    dp 找规律 我好菜啊好菜啊,完全没有思路. 在合法的括号序列中,左括号数一定大于等于右括号数的,所以我们可以先定义平衡度为左括号数-右括号数. 然后可以发现一个惊人的规律..就是在trie同一深度上 ...

  8. PAT——1074. 宇宙无敌加法器(20)

    地球人习惯使用十进制数,并且默认一个数字的每一位都是十进制的.而在PAT星人开挂的世界里,每个数字的每一位都是不同进制的,这种神奇的数字称为“PAT数”.每个PAT星人都必须熟记各位数字的进制表,例如 ...

  9. XAMPP启动mysql问题

    Problem detected!21:57:44 [mysql] Port 3306 in use by ""E:\MySQL\bin\mysqld" --defaul ...

  10. CH4402 小Z的袜子(莫队)

    描述 作为一个生活散漫的人,小Z每天早上都要耗费很久从一堆五颜六色的袜子中找出一双来穿.终于有一天,小Z再也无法忍受这恼人的找袜子过程,于是他决定听天由命-- 具体来说,小Z把这N只袜子从1到N编号, ...