当前位置: 首页 > 科技观察

如何在C#中重写Object的虚方法

时间:2023-03-20 20:19:49 科技观察

【.com原稿】在C#中,Object是所有类的基类,所有的结构和类都直接或间接地派生自它。前面这段话可以说是所有C#开发者都知道的,但是我相信他们中的一些人不知道甚至不知道我们常用的ToString、Equals和GetHashCode虚方法都是来自于Object类,我们可以修改它们改写。重写这三个虚方法可以说是项目开发中经常用到的,但是大部分开发者并没有注意到这三个虚方法是可以重写的,而是自己写方法来实现。下面我详细解释一下这三个应该怎么改写。这里需要说明的是,本文会涉及到很多设计规范和设计需求,代码只会作为辅助理解出现,所以文中所有代码都会以代码段的形式出现。1.ToStringToString重写是这三种方法中最简单也是最常用的。但是有些开发者认为重写ToString方法意义不大,所以这里要说的是,这种想法是错误的。当我们在对象上调用ToString时,默认返回类的完全限定名。例如,如果我们在System.IO.File对象上调用此方法,将返回字符串System.IO.File。这个结果往往不是我们想要的结果,这个结果也是没有意义的。比如我们重写了一个User类中的ToString方法,每次调用User.ToString()都会返回“XXX今年XX岁”。如果我们不重写ToString方法,我们将得不到我们想要的结果。所以我们必须重写,这是我们此时可以做的。publicclassUser{publicintId{get;set;}publicstringName{get;set;}publicintAge{get;set;}publicstringSex{get;set;}publicoverridestringToString(){return$“{Name}今年是{Age}!”;}}改写后,我们可以得到我们想要的输出。虽然重写ToString可以得到我们想要的,但是我们不能在任何情况下重写ToString,只有在以下三种情况下才可以重写ToString:代码的最终用户是开发人员;需要写日志;IDE调试输出。上述三种情况下重写ToString,我们还是需要遵循一些设计规范。这些设计规范不是微软定义的,而是开发者在开发过程中总结出来的:ToString返回的字符串长度要短,内容描述要清楚;ToString方法不返回"",而是返回null;不要在ToString方法中引发和抛出异常,应及时捕获并处理异常;如果返回值有地域文化(如语言)或格式要求,则必须重写ToString方法;ToString必须返回一个唯一的字符串来标识重写后的实例对象。至此我们已经讲解了ToString重写的方法和规则。相对而言,ToString方法重写是Object虚方法重写中非常简单的部分。作为开发人员,你只需要按照我前面说的规则、方法和实际情况进行重写即可。2.如果Equals和ReferenceEquals在C#中判断两个对象是否相等,有两种情况:判断两者的值相等或者判断两者的引用地址相同。一般来说,我们需要判断值类型对象的值是否相等,判断引用类型对象的指向地址是否相同。equals用于判断引用类型对象指向的地址是否相同。许多开发人员认为重写Equals方法很容易,但他们在开发过程中往往会忘记一些重要的细节。这些细节对程序至关重要。我将在下面详细解释它们。同一性与相等性所谓同一性,是指如果两个对象引用同一个实例,那么我们就说这两个对象具有同一性。在C#中,我们可以使用对象类或其派生类中的ReferenceEquals静态方法来判断对象之间的同一性。但相同只是一种相等,因为在某些情况下,两个对象的部分或全部值相等但引用不同,我们也可以说它们具有相等性。我们来看一个通过重写相等来实现两个对象相等的例子。classProgram{staticvoidMain(string[]args){Students1=newStudent{Age=12,Id=1,Name="小明"};Students2=newStudent{Age=13,Id=1,Name="小明"};if(Student.ReferenceEquals(s1,s2)){Console.WriteLine("是同一个学生");}else{Console.WriteLine("不是同一个学生");}Console.Read();}}classStudent{publicintId{get;set;}publicstringName{get;set;}publicintAge{get;set;}publicstaticboolReferenceEquals(Students1,Students2){if(s1.Equals(s2)||object.ReferenceEquals(s1,s2)||s1.Id==s2.Ids1==s2){returntrue;}else{returnfalse;}}}从上面的代码可以看出,虽然s1和s2的引用不相等,但是两个对象使用了相同的Id,因此,我们将具有相同Id的学生视为同一学生。这样做可确保数据库中没有重复的条目。提示:只有引用类型可以具有引用相等性。对于值类型,调用ReferenceEquals方法总会返回false,因为值类型在转换为对象时需要进行装箱,即传递的两个参数是同一个值,同样返回false。equals判断两个对象是否相等,可以使用Equals,通过它可以判断两个对象是否有相同的数据。在object中,这个方法只是调用了ReferenceEquals方法来判断身份,所以必要的时候我们必须重写Equals方法。一般来说,重写Equals方法的常见步骤如下:检查对象是否为null;判断是否为引用类型,如果是则判断引用是否相等;判断数据类型是否相等;调用特定类型的辅助方法,参数必须是比较类型;判断哈希码是否相等,这一步需要进行短路操作和字段比较;在基类的Equals方法被重写的前提下,必须检查基类的Equals方法;判断关键字段的值是否相等;重写GetHashCode方法;重写==,!=运算符。提示:如果类型是密封型,那么第三步可以省略。我们不仅需要按照上面的步骤重写Equals方法,还需要注意以下几点:GetHashCode方法返回的值不一定是唯一的,不能仅仅依靠判断两个对象是否相等它的返回值;在GetHashCode和Equals中抛出任何异常;必须保证可以随意比较对象而不触发任何异常;equals、GetHashCode、==和!=必须重写,并且重写的算法必须相同;尽量不要使用变量覆盖类型上的相等运算符。3、GetHashCode在上一节中,我们也注意到在重写Equals的过程中需要重写GetHashCode方法。所谓HashCode就是用来生成一个对应对象值的数字,从而高效地平衡哈希表的作用。重写GetHashCode方法比较困难。下面我详细解释一下重写规则、方法和注意事项。重写GetHashCode方法需要考虑性能和安全性,也需要满足一些要求。性能由于哈希码的返回值是int类型,可能会出现某些对象包含的值大于int值范围的情况。这时候哈希码肯定会有重复,所以我们要保证哈希码的返回值尽可能唯一。另外,对于哈希码的算法,尽量保证返回的哈希码在int类型的取值范围内均匀分布。在使用Equals中的GetHashCode方法进行短路运算时,一定要优化算法的性能,避免在字典集合中使用类型作为key类型,因为这样会导致频繁调用GetHashCode方法。在设计GetHashCode的算法时,要保证一个很好的平衡,即无论哈希表如何对哈希值进行分桶,都不会破坏平衡。一般来说,最理想的状态是两个对象相差1位,哈希码应该相差16位。安全性在安全性方面,首先要遵循的是哈希码对象很难伪造。一般来说,攻击者会向哈希表中写入大量具有相同哈希值的数据。如果哈希表效率不高,就会受到拒绝服务攻击。我们一般对相关类型的哈希码进行异或运算,并保证操作数不相似或不相等。如果操作数相近或相等,则应考虑移位和加法运算。但是,多次使用and运算符会导致hash值为0,而多次使用or运算符会导致hash值为1,需要注意。更进一步,我们应该在开发中使用移位运算符来分解比int更大的类型。要求要求是性能和安全性的基础。只有完全符合要求,性能和安全才能发挥作用。第一个要求也是最基本的优势。相同的对象具有相同的哈希码。其次,在特定的生命周期内,特定对象的GetHashCode的返回值总是相同的。最后,GetHashCode不能导致任何异常。If异常还必须返回一个值以指示内部异常。4.小结本文主要讲解重写object中虚方法的知识,涉及到很多C#核心内容。这些内容和知识在实际开发中用的比较多,但是大部分开发者并不关心,所以希望读者在看完这篇文章后,能够对这些内容和知识有一个初步的了解。作者简介:朱刚,化名苗叔,国内某技术博客认证专家,.NET高级开发工程师。曾就职于初创公司,从事企业级安全监控系统开发。【原创稿件,合作网站转载请注明原作者和出处为.com】