在 C# 中 Object 是一切类的基类,一切的结构和类都直接或直接的派生自它。前面这段话能够说一切的 C# 开发人员都知道,可是我信任其间有一部分程序员并不清楚乃至不知道咱们常用的 ToString 、 Equals 和 GetHashCode 虚办法都来自于 Object 类,而且咱们能够对它们进行重写。重写这三个虚办法能够说在项目开发中经常用到,只不过大部分开发人员并未留心这三个虚办法能够重写,而是自己写办法来完成。
下面我就来具体解说一下它们三个应该怎样重写。在这儿我需求阐明的是本篇文章会许多触及到规划规范和规划要求,代码只是作为辅佐了解的方式呈现,因而文章中的一切代码将会以代码段的方式呈现。
一、ToString
ToString 重写是这三种办法中重写最简略的,也是最常用的。可是有一部分开发人员以为重写 ToString 办法含义不大,那么我在这儿要说的是这种主意是过错的。当咱们在目标上调用 ToString 时默许回来的是类的彻底限制称号,比方说咱们在 System.IO.File 目标上调用这个办法,就会回来字符串 System.IO.File ,这个成果往往并不是咱们所需求的成果而且这个成果也没有什么含义。例如咱们在一个 User 类中重写 ToString 办法,每次调用 User.ToString() 时回来 “XXX本年XX岁”,假如咱们不重写 ToString 办法的话就得不到咱们想要的成果。因而咱们有必要重写,这时咱们就能够这么写。
public class User
{
public int Id {get;set;}
public string Name {get;set;}
public int Age {get;set;}
public string Sex {get;set;}
public override string ToString()
{
return $“{Name}本年{Age}岁!”;
}
}
重写之后咱们就能够得到咱们想要的输出内容了。尽管重写 ToString 能够得到咱们想要的内容,可是咱们不能在任何状况下都重写 ToString, 只需在以下三种状况下方可重写 ToString :
代码面临的最终用户是开发人员;
需求写入日志;
IDE调试输出。
在上面三种状况下重写 ToString 咱们还需求遵从一些规划规范,这些规划规范并不是微软所界说的,而是开发人员在开发过程中总结出来的:
ToString 回来的字符串长度应该简略,内容描绘应该明晰;
不要从 ToString 办法中回来 “”,而要回来 null ;
不要再 ToString 办法中引发并抛出反常,针对反常应该及时捕获并处理;
假如回来值存在地域文明(比方言语)或存在格式化要求,那么就有必要重写 ToString 办法;
ToString 重写后有必要回来绝无仅有的字符串来标识实例目标。
到这儿停止咱们解说完了 ToString 重写的办法以及规矩。相对来说 ToString 办法重写是 Object 虚办法重写中非常简略的部分,作为开发人员只需依照我前面多说的规矩、办法以及实践状况来重写即可。
二、 Equals 和 ReferenceEquals
在 C# 中假如对两个目标进行持平判别,一共有两种状况分别是:判别两者的值持平 或许 判别两者的引证地址相同 。一般状况下咱们需求对值类型目标判别值持平,对引证类型目标判别指向地址相同。Equals 便是用来对引证类型目标判别指向地址是否相同的。关于重写 Equals 办法,许多开发人员以为一挥而就,可是在开发中往往忘掉一些很重要的细节,这些细节关于程序来说至关重要,下面我将逐个进行具体解说。
同一和持平 所谓的同一指的是两个目标假如引证的是同一个实例,那么咱们就说这两个目标具有同一性。在 C# 中咱们能够运用 object 类或许它的派生类中的 ReferenceEquals 静态办法来判别目标之间的同一性。可是同一只是持平的一种,由于在某些状况下两个目标的部分值或许悉数值持平但引证不同,咱们也能够说它们具有持平性。下面咱们来看一个比如,这个比如经过重写持平性来完成两个目标的持平性。
class Program
{
static void Main(string[] args)
{
Student s1 = new Student
{
Age = 12,
Id = 1,
Name = “小明”
};
Student s2 = new Student
{
Age = 13,
Id = 1,
Name = “小明”
};
if (Student.ReferenceEquals(s1, s2))
{
Console.WriteLine(“是同一个学生”);
}
else
{
Console.WriteLine(“不是同一个学生”);
}
Console.Read();
}
}
class Student
{
public int Id { get; set; }
public string Name { get; set; }
public int Age { get; set; }
public staTIc bool ReferenceEquals(Student s1, Student s2)
{
if (s1.Equals(s2) ||
object.ReferenceEquals(s1, s2) ||
s1.Id==s2.Id
s1==s2)
{
return true;
}
else
{
return false;
}
}
}
从上述代码中咱们能够看出,尽管 s1 和 s2 引证是不持平的,可是这两个目标运用了相同的 Id ,因而咱们以为 Id 相同的学生便是同一个学生。这么做能够确保数据库中不会呈现重复的录入。
TIp:只需引证类型才会或许呈现引证持平的状况,关于值类型来说调用 ReferenceEquals 办法永久回来的是 false ,由于值类型转换成 object 时是需求装箱的,便是传递的两个参数是同一个值,也会回来 false 。
Equals 判别两个目标是否持平,能够运用 Equals ,经过它能够判别出两个目标是否具有相同的数据。在 object 中这个办法只是调用了 ReferenceEquals 办法来判别同一性,因而在必要的时分咱们有必要重写 Equals 办法。一般来说重写 Equals 办法常用的过程如下:
查看目标是否为 null ;
判别是否是引证类型,假如是就判别引证是否持平;
判别数据类型是否持平;
调用具体类型的辅佐办法,参数有必要是要比较的类型;
判别哈希码是否持平,这一步需进行短路操作和字段比较;
在基类的 Equals 办法被重写的前提下,有必要查看基类的 Equals 办法;
判别要害字段的值是否持平;
重写 GetHashCode 办法;
重写 == 、 != 操作符。
TIp: 假如类型是密封类型,那么第三步能够省掉掉。
咱们不只需求依照上述的过程重写 Equals 办法,还需求留意如下几点:
GetHashCode 办法不一定回来的是绝无仅有的值,因而咱们不能只是依靠它的回来值来判别两个目标是否持平;
咱们不能在 GetHashCode 和 Equals 中引发任何反常;
有必要确保目标之间能够随意比较,且不能触发任何反常;
有必要完成重写 Equals 、 GetHashCode 、 == 和 != ,且重写的算法有必要相同;
尽量不要在可变类型上重写持平性操作符。
三、 GetHashCode
在上一末节中咱们也留意到在重写 Equals 过程中咱们需求重写 GetHashCode 办法。 所谓 Hash Code 便是用来生成和目标值对应的数字,然后高效的平衡哈希表的效果。 重写 GetHashCode 办法是比较困难的,下面我就来具体解说一下重写规矩、办法和留意事项。重写 GetHashCode 办法需求从功能、安全方面考虑,一起也需求满意一些要求。
功能 由于哈希码的回来值是 int 类型,因而会呈现部分目标包括的值比 int 取值规模大的状况,这时哈希码就肯定会存在重复的状况,所以这时咱们要确保哈希码的回来值尽或许的仅有。此外针对哈希码的算法咱们要尽或许的确保回来的哈希码应当在 int 类型取值规模内均匀散布。在 Equals 中运用 GetHashCode 办法进行短路操作时咱们有必要对算法的功能进行优化,防止将类型作为字典调集中的键类型运用,由于这会导致频频的调用 GetHashCode 办法。在规划 GetHashCode 的算法时应确保杰出的平衡性,即不管哈希表怎么对哈希值进行 buckeTIng,也不会损坏平衡性。一般来说最理想的状况是两个目标间 1 bit 的差异应该形成哈希码 16 bit 的差异。
安全 在安全性这方面首先应该遵从的是难以假造哈希码目标,一般来说进犯者会向哈希表中写入许多哈希值相同的数据,这时假如哈希表完成功率不高将会收到拒绝服务进犯。咱们一般会历来自相关类型的哈希码运用异或操作,且确保操作数不附近或许持平。假如呈现操作数附近或许持平的状况,那么应该考虑运用位移和加法操作。可是屡次运用 and 操作符会呈现哈希值为 0 的状况,而屡次运用 or 操作符则会呈现哈希值为 1 的状况,这一点需求留意一下。更进一步的做法是,咱们在开发中应该运用移位操作符来分化比 int 大的类型。
要求 要求是功能和安全的根底,只需彻底符合了要求的规则,功能和安全才干很好的起效果。要求的第一点也是最根底的长处,持平的目标它们的哈希码也持平,其次在特定的生命周期内,特定目标的 GetHashCode 的回来值始终是相同的,最终 GetHashCode 不能引发任何反常,假如其间呈现反常也有必要回来一个值来表明内部呈现反常。
四、总结
本篇文章首要解说了重写 object 中虚办法的常识,其间触及到了许多 C# 核心内容,这些内容和常识在实践开发顶用的许多,可是大多数开发人员并不介意,因而我期望读者阅读完我这篇文章后能对这些内容和常识有开始的了解。
责任编辑:Ct