漫谈Object基类(一)

引言

     Object类是C#中所有类型的基类,但由于对它的继承是隐式的,故大多数人对它并不太在意,其实object中包含了很多有用的方法,对它有个清晰的了解能够很好地帮你理清楚c# API的层次结构,本文就Object中6个方法进行简单的说明,重点讲述Equals方法。

综述

     在.Net中,每一个类型都继承自一个公共的基类:System.Object。Object类定义了.NET世界中每一个类型都支持的一组公共的成员集合。当创建任何一个不显示指定其基类的类时,它隐含继承自System.Object,当然,你也可以显示地继承。下面来看一下,System.Object的接口:

ContractedBlock.gif ExpandedBlockStart.gif Code
public class Object
{
    
public Object();
    
public virtual Boolean Equals(Object obj);
    
public virtual Int32 GetHashCode();
    
public Type GetType();
    
public virtual String ToString();
    
protected virtual void Finalize();
    
protected Object MemberwiseClone();
    
public static bool Equals(object objA, object objB);
    
public static bool ReferenceEquals(object objA, object objB);
}

     可以看出,Object类主要包含四个虚方法,子类可以重写它,应该说这几个是比较常用的(除了Finalize());两个静态方法,object对其有具体实现,可以直接使用;两个实例方法,GetType在反射中用的比较多,MemberwiseClone在实现ICloneable接口时比较常用。至此,相信大家对object应该有了一个整体的认识,下面分别对它的几个方法成员进行描述。

应用

1 ToString() : 自描述

     用一句话来概括ToString()方法的作用,我觉得应该是“提供了一种获得对象当前状态的快照”。object类对它的默认实现是返回该对象的完全限定名,也就是说,如果你定义的类没有重写ToString()方法,那么直接调用当前对象的ToString()方法,返回的是一个字符串:命名空间+类名。如下面的代码,输出的结果是: ConsoleApplication.Program

ContractedBlock.gifExpandedBlockStart.gif Code
namespace ConsoleApplication
{
    
public class Program
    {
        
static void Main(string[] args)
        {
            Program p 
= new Program();
            Console.WriteLine(p.ToString()); 
        }
    }
}

     上面已经说了,ToString()方法主要用来返回对象当前状态的一个快照,故如果我们在项目中有这种需求,就应该重写该方法。下面写个简单的代码实例,后面几个方法的说明都会围绕这个实例说明。如果做网络协议方面的开发,经常会遇到各种各样的Error Code,下面我们设计一个简单的类,用来描述一个网络操作的返回结果,代码如下:

ContractedBlock.gif ExpandedBlockStart.gif Code
public class Status
    {
        
//Indicate the type of error.
        private int errorCode;
        
//Description for error.
        private string errorString;

        
#region Constructors
        
public Status()
        {
            errorCode 
= 0;
            errorString 
= string.Empty;
        }

        
public Status(int errorCode)
        {
            
this.errorCode = errorCode;
        }

        
public Status(int errorCode, string errorString)
            : 
this(errorCode)
        {
            
this.errorString = errorString;
        }

        
#endregion

        
#region Properties
        
public int ErrorCode
        {
            
get
            {
                
return errorCode;
            }
            
set
            {
                errorCode 
= value;
            }
        }

        
public string ErrorString
        {
            
get
            {
                
return errorString;
            }
            
set
            {
                errorString 
= value;
            }
        } 
        
#endregion       

    }

     Status类包括两个数据成员,errorCode 和 errorString,分别用来描述错误码和错误提示信息,这里是为了简单化,实际的开发中errorCode一般是设计成enum类型的,易于标识。接着,我们在Status类中重写ToString()方法,代码如下:

ContractedBlock.gif ExpandedBlockStart.gif Code
public override string ToString()
        {
            StringBuilder sb 
= new StringBuilder();
            sb.AppendFormat(
"ErrorCode = {0};"this.errorCode);
            sb.AppendFormat(
"ErrorString = {0}"this.errorString);
            
return sb.ToString();
        }

     代码很简单,就是将当前对象的数据以一定的形式输出来,下面我们写个客户端代码简单测试下:

ContractedBlock.gif ExpandedBlockStart.gif Code
static void Main(string[] args)
        {
            Status status1 
= new Status(1"ErrorString1");
            Status status2 
= new Status(2"ErrorString2");

            Console.WriteLine(status1.ToString());
            Console.WriteLine(status2.ToString());  
        }

返回结果如下:

     ErrorCode = 1;ErrorString = ErrorString1
     ErrorCode = 2;ErrorString = ErrorString2

小结:ToString()方法主要用来描述对象的当前状态,在某些特定应用中非常有用,比如在一些网络协议中,我们需要以Http Get的方式发送数据给服务器,那么构建url就是个必须的工作,如果只是简单几个参数可能比较容易构建,但是一旦需要传递的参数很多,那么比较OO的做法就是构建一个类,将那些需要传递的数据作为数据成员封装好,然后重写ToString()方法,将对象的数据按照url要求的格式组织好返回。

2 Equals(): 判等

     Equals相关的方法总共有三个,Equals(Object, Object) : Boolean;  ReferenceEquals(Object, Object) : Boolean; Equals(Object) : Boolean。前两个方法object已经提供了具体的实现,对于ReferenceEquals,顾名思义,是用来判断两个对象的引用是否相同,需要注意两点:

     1 如果传入两个null对象,返回true。

     2 由于ReferenceEquals的两个参数都是Object类型的,如果传入值类型将会进行装箱,由此会导致引用不相同,比如代码:Object.ReferenceEquals(2, 2),返回的是false,因为对2进行装箱以后倒置两者指向了不同的引用。如果这样写呢:int n = 2; Object.ReferenceEquals(n, n); 返回的依然是false。

     接着,简单说明一下Equals(Object, Object) : Boolean,.NET中对它的实现如下:

ContractedBlock.gif ExpandedBlockStart.gif Code
public static bool Equals(object objA, object objB)
{
    
return ((objA == objB) || (((objA != null&& (objB != null)) && objA.Equals(objB)));
}

流程如下:    

    判断两个对象是否指向同一个引用(包括两者为null的情况),如果是则返回true,否则继续进行;

    如果两者都不为null的时候,返回的结果取决于实例方法Equals的返回值。

object对Equals(Object)的默认实现是只有当两个对象指向相同的引用才返回true,下面我们来对该方法进行重载,代码依旧在Status的基础上改,加一个方法,代码如下:

ContractedBlock.gif ExpandedBlockStart.gif Code
public override bool Equals(object obj)
        {
            
if (obj != null && obj is Status)
            {
                Status temp 
= obj as Status;

                
if (temp.errorCode == this.errorCode &&
                    temp.errorString 
== this.errorString)
                {
                    
return true;
                }
            }
            
return false;
        }

     先判断obj对象是否为null以及它的类型,如果它是一个非空的Status类型则比较对象的值,相等则返回true。修改Main方法,对Equals方法进行测试:

ContractedBlock.gif ExpandedBlockStart.gif Code
static void Main(string[] args)
        {
            Status status1 
= new Status(1"ErrorString1");
            Status status2 
= new Status(1"ErrorString1");

            Console.WriteLine(status1.Equals(status2)); 
        }

输出结果为True,倘若没有重写Equals方法,输出的将是False,因为两者的状态数据相同,但是指向不同的引用。

重写了Equals方法后,重载 == 和 != 运算符其实也是件很容易的时,直接调Equals方法即可,但是引用《Effective c#》中的建议:

     1 定义==重载函数的时候,也要定义!=重载函数。     

     2 值类型最好不要重载定义Equals函数,而引用类型最好不要重载定义==操作符。

至此,有关Equals就介绍到这,相信大家对它应该有了一个清晰的认识,接着我们来看Object下一个与Equals密切相关的成员方法。

3 GetHashCode(): 对象地址

     GetHashCode()方法返回一个能够标识内存中指定对象的整数,如果你打算将自定义的类型包含进System.Collections.HashTable类型中,强烈建议你重写这个方法的默认实现。当我们重写了Equals(Object)实例方法后,编译器会产生一个警告,建议你同时也重写GetHashCode方法。GetHashCode的作用是返回一个数值,又叫散列码,它根据对象的内部状态数据表识对象。因此,如果两个对象的状态数据相同,也应该获得相同的散列码。一般来说,重写GetHashCode只在打算将自定义的类型保存在一个基于散列值的集合中时有用。在底层,HashTable类型调用所含类型的GetHashCode()以及Equals()成员来确定要返回给调用者的正确对象。

     创建散列码的算法有很多,这里不做说明,System.String类提供了一个可靠的GetHashCode()的实现,它基于字符串的字符数据。如果能确定某个字符串字段在对象之间是唯一的,例如id,那么就可以直接对这个字段的字符串调用GetHashCode();如果找不到这样一个唯一型字段,但已经重写了ToString(),可以直接从它GetHashCode(),对于Status类,由于我们已经实现了ToString()方法,就可以这样简单实现GetHashCode方法,代码如下:

ContractedBlock.gif ExpandedBlockStart.gif Code
public override int GetHashCode()
        {
            
return ToString().GetHashCode();
        }

     有关HashCode是否有效的判断标准,引用《Effective C#》中描述如下:  

     1. 如果两个对象相等(由operator==定义),它们必须产生相同的散列码。否则,这样的散列码不能用来查找容器中的对象[22]。

     2. 对于任何一个对象A,A.GetHashCode()必须是一个实例不变式(invariant)。即不管在A上调用什么方法,A.GetHashCode()都必须总是返回相同的值。这可以确保放在“散列桶”中的对象总是位于正确的“散列桶”中。

     3. 对于所有的输入,散列函数应该在所有整数中产生一个随机的分布。这样,我们才能从一个散列容器上获得效率的提升。

     那么如果两个对象不相等能否产生相同的散列码呢? 网上搜了一下,看到有网友发的一段代码,似乎对于不同的状态数据,是可以返回相同的散列码,不信运行下面代码试一下:

ContractedBlock.gif ExpandedBlockStart.gif Code
Console.WriteLine("0.89265452879139".GetHashCode());
Console.WriteLine(
"0.280527401380486".GetHashCode());   

返回的结果都是:2060653827

     想要了解更多有关GetHashCode()方法的描述,可以参考《Effective C#: 改善C#程序的50种方法》

转载于:https://www.cnblogs.com/lemonade/archive/2008/12/02/1346274.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值