最后的据点

Think it,Plan it,Build it,Make it,Happy it,Enjoy it! 格物,诚意,志存高远,脚踏实地,让精益求精成为习惯!
  博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

System.Object类型剖析

Posted on 2009-06-21 12:38  最后的据点  阅读(3222)  评论(2编辑  收藏  举报

在C#中,Object类型是所有类型的根,大家平常开发中都要跟它打交道,但不见得对它里面的每个方法都知根知底,下面对它里面的每个方法都进行仔细的总结。

概述:

构造函数

Equals函数

Finalize函数

GetHashCode函数

GetType()函数

ReferenceEquals函数

MemberWiseClone()函数

ToString()函数

Object类型中一共有8个方法,重载的方法没有算进来。下面一一来看看这些方法。

1、构造函数   返回

函数签名:public Object()

作用:这个就不用多说了

注意点:直接使用new Object()可以用来创建对象;如果非Object类型,则在该类型的构造函数被调用时,该函数自动被调用。

2、Equals函数,该方法有重载函数   返回

函数签名:public virtual bool Equals(Object obj)

             public static bool Equals(Object objA,Object objB)

先来看看public virtual bool Equals(Object obj),从签名里就可以看出来,这个方法是用来开放给继承类去根据实际情况重写的。

注意点:

1)默认情况下,对于引用类型,该方法对比的时引用的地址,对于值类型,对比的是值的二进制描述是否相同。100.0与100虽然都是100,但是它们的二进制分布是不一样的。

2)不管是引用类型还是值类型,重写该方法的主要目的都是实现根据值来判断对象是否相等。

3)该方法本身只支持原子类型的比较以及简单对象的比较,如果是自定义复杂的类型,则有必要重写该方法。

4)重写该方法时,应满足一些规则。

首先x.Equals(x)应该返回true;

x.Equals(y)的结果应该与y.Equals(x)相同;

x.Equals(y)返回true,如果x和y都是Nan;

如果x.Equals(y) && y.Equals(z),则x.Equals(z)应该返回true;

x.Equals(null)都返回false;

5)在重写Equals()方法时,不应该抛出异常;

6)实现IComparable接口的话,一定记得重写Equals方法;

7)如果重写了Equals(),则一般情况也必须重写GetHashCode(),不然如果把该类型的对方放在hashtable结构上的话可能产生问题;

8)如果对==进行了操作符重载,则记得一定重写Equals();按照这样的设计方法,类型代码在调用Equals时的运行结果,能够跟应用程序调用==时的结果保持一致。

看完了public virtual bool Equals(Object obj),我们接着来看看它的重载方法public static bool Equals(Object objA,Object objB)

该方法返回true的条件:

1)objA和objB是同一个实例

2)objA和objB都是null

3)objA.Equas(objB)返回true

注意上面三个条件是||的关系

对比这两个Equals方法,还是很容易对比出不同点的。

3、Finalize函数   返回

函数签名:protected virtual void Finalize()

作用:允许对象在垃圾回收回收该对象之前尝试释放资源并执行其它清理操作。

注意点:

Finalize 是受保护的,因此只能通过此类或派生类访问它。

对象变为不可访问后将自动调用此方法,除非已通过 SuppressFinalize 调用使对象免除了终结。在应用程序域的关闭过程中,对没有免除终结的对象将自动调用 Finalize,即使那些对象仍是可访问的。对于给定的实例仅自动调用 Finalize 一次,除非使用 ReRegisterForFinalize 这类机制重新注册该对象并且后面没有调用 GC.SuppressFinalize。

派生类型中的每个 Finalize 实现都必须调用其基类型的 Finalize 实现。这是唯一一种允许应用程序代码调用 Finalize 的情况。

Finalize 操作具有下列限制:

  • 垃圾回收过程中执行终结器的准确时间是不确定的。不保证资源在任何特定的时间都能释放,除非调用 Close 方法或 Dispose 方法。

  • 即使一个对象引用另一个对象,也不能保证两个对象的终结器以任何特定的顺序运行。即,如果对象 A 具有对对象 B 的引用,并且两者都有终结器,则当对象 A 的终结器启动时,对象 B 可能已经终结了。

  • 运行终结器的线程是未指定的。

在下面的异常情况下,Finalize 方法可能不会运行完成或可能根本不运行:

  • 另一个终结器无限期地阻止(进入无限循环,试图获取永远无法获取的锁,诸如此类)。由于运行时试图运行终结器来完成,所以如果一个终结器无限期地阻止,则可能不会调用其他终结器。

  • 进程终止,但不给运行时提供清理的机会。在这种情况下,运行时的第一个进程终止通知是 DLL_PROCESS_DETACH 通知。

在关闭过程中,只有当可终结对象的数目继续减少时,运行时才继续 Finalize 对象。

如果 Finalize 或 Finalize 的重写引发异常,并且运行库并非寄宿在重写默认策略的应用程序中,则运行库将终止进程,并且不执行任何活动的 try-finally 块或终结器。如果终结器无法释放或销毁资源,此行为可以确保进程完整性。

对实现者的说明:

默认情况下,Object..::.Finalize 不执行任何操作。只有在必要时才必须由派生类重写它,因为如果必须运行 Finalize 操作,垃圾回收过程中的回收往往需要长得多的时间。

如果 Object 保存了对任何资源的引用,则 Finalize 必须由派生类重写,以便在垃圾回收过程中,在放弃 Object 之前释放这些资源。

当类型使用文件句柄或数据库连接这类在回收使用托管对象时必须释放的非托管资源时,该类型必须实现 Finalize。有关辅助和具有更多控制的资源处置方式,请参见 IDisposable 接口。

Finalize 可以采取任何操作,包括在垃圾回收过程中清理了对象后使对象复活(即,使对象再次可访问)。但是,对象只能复活一次;在垃圾回收过程中,不能对复活对象调用 Finalize。

析构函数是执行清理操作的 C# 机制。析构函数提供了适当的保护措施,如自动调用基类型的析构函数。

4、GetHashCode函数   返回

函数签名:public virtual int GetHashCode()

作用:用作特定类型的哈希函数。

注意点:

GetHashCode 方法适用于哈希算法和诸如哈希表之类的数据结构。

GetHashCode 方法的默认实现不保证针对不同的对象返回唯一值。而且,.NET Framework 不保证 GetHashCode 方法的默认实现以及它所返回的值在不同版本的 .NET Framework 中是相同的。因此,在进行哈希运算时,该方法的默认实现不得用作唯一对象标识符。

GetHashCode 方法可以由派生类型重写。值类型必须重写此方法,以提供适合该类型的哈希函数和在哈希表中提供有用的分布。为了获得最佳结果,哈希代码必须基于实例字段或属性(而非静态字段或属性)的值。

用作 Hashtable 对象中键的对象还必须重写 GetHashCode 方法,因为这些对象必须生成其各自的哈希代码。如果用作键的对象不提供 GetHashCode 的有用实现,您可以在构造 Hashtable 对象时指定哈希代码提供程序。在 .NET Framework 2.0 版之前,哈希代码提供程序是基于 System.Collections..::.IHashCodeProvider 接口的。从 2.0 版开始,哈希代码提供程序基于 System.Collections..::.IEqualityComparer 接口。

对实现者的说明:

哈希函数用于快速生成一个与对象的值相对应的数字(哈希代码)。哈希函数通常是特定于每个 Type 的,而且,必须至少使用一个实例字段作为输入。

哈希函数必须具有以下特点:

  • 如果两个对象的比较结果相等,则每个对象的 GetHashCode 方法都必须返回同一个值。但是,如果两个对象的比较结果不相等,则这两个对象的 GetHashCode 方法不一定返回不同的值。

  • 一个对象的 GetHashCode 方法必须总是返回同一个哈希代码,但前提是没有修改过对象状态,对象状态用来确定对象的 Equals 方法的返回值。请注意,这仅适用于应用程序的当前执行,再次运行该应用程序时可能会返回另一个哈希代码。

  • 为了获得最佳性能,哈希函数必须为所有输入生成随机分布。

例如,String 类提供的 GetHashCode 方法的实现为相同的字符串值返回相同的哈希代码。因此,如果两个 String 对象表示相同的字符串值,则它们返回相同的哈希代码。另外,该方法使用字符串中的所有字符生成相当随机的分布式输出,即使当输入集中在某些范围内时(例如,许多用户可能有只包含低位 128 个 ASCII 字符的字符串,即使字符串可以包含 65,535 个 Unicode 字符中的任何字符)。

对于 Object 的派生类,当且仅当此派生类将值相等性定义为引用相等并且类型不是值类型时,GetHashCode 方法才可以委托给 Object..::.GetHashCode 实现。

在类上提供好的哈希函数可以显著影响将这些对象添加到哈希表的性能。在具有好的哈希函数实现的哈希表中,搜索元素所用的时间是固定的(例如运算复杂度为 O(1) 的运算)。而在具有不好的哈希函数实现的哈希表中,搜索性能取决于哈希表中的项数(例如运算复杂度为 O(n) 的运算,其中的 n 是哈希表中的项数)。哈希函数的计算成本也必须不高。

GetHashCode 方法的实现必须不会导致循环引用。例如,如果 ClassA.GetHashCode 调用 ClassB.GetHashCode,ClassB.GetHashCode 必须不直接或间接调用 ClassA.GetHashCode。

GetHashCode 方法的实现必须不引发异常。

重写 GetHashCode 的派生类还必须重写 Equals,以保证被视为相等的两个对象具有相同的哈希代码;否则,Hashtable 类型可能无法正常工作。

示例

在某些情况下,GetHashCode 方法的实现只返回整数值。下面的代码示例阐释了返回整数值的 GetHashCode 的实现。

using System;
 
public struct Int32 {
   public int value;
 
   //other methods...
 
   public override int GetHashCode() {
      return value;
   }
}

一个类型常具有多个可以参与生成哈希代码的数据字段。生成哈希代码的一种方法是使用 XOR (eXclusive OR) 运算合并这些字段,如下面的代码示例所示。

using System;
 
public struct Point {
   public int x;
   public int y; 
 
   //other methods
 
   public override int GetHashCode() {
      return x ^ y;
   }
}

下面的代码示例阐释了另一种情况:使用 XOR (eXclusive OR) 合并该类型的字段以生成哈希代码。注意,在该代码示例中,字段表示用户定义的类型,每个类型都实现 GetHashCode 和 Equals。

using System;
 
public class SomeType {
   public override int GetHashCode() {
     return 0;
   }
}
 
public class AnotherType {
   public override int GetHashCode() {
     return 1;
   }
}
 
public class LastType {
   public override int GetHashCode() {
     return 2;
   }
}
 
public class MyClass {
   SomeType a = new SomeType();
   AnotherType b = new AnotherType();
   LastType c = new LastType();
 
   public override int GetHashCode () {
     return a.GetHashCode() ^ b.GetHashCode() ^ c.GetHashCode();
   }
}

如果派生类的数据成员比 Int32 大,则可以使用 XOR (eXclusive OR) 运算合并该值的高序位和低序位,如下面的代码示例所示。

using System;
 
public struct Int64 {
   public long value;
 
   //other methods...
 
   public override int GetHashCode() {
      return ((int)value ^ (int)(value >> 32));
   }
}

5、GetType()函数   返回

函数签名:public Type GetType()

作用:获取当前实例的确切运行时类型。

注意点:

对于具有相同运行时类型的两个对象 x 和 y,虽然Object.ReferenceEquals(x,y)返回的是false,但是 Object.ReferenceEquals(x.GetType(),y.GetType()) 返回 true。

Type 对象公开与当前 Object 的类关联的元数据。

下面的代码示例说明 GetType 返回当前实例的运行时类型。

using System;
 
public class MyBaseClass: Object {
}
 
public class MyDerivedClass: MyBaseClass {
}
 
public class Test {
 
   public static void Main() {
      MyBaseClass myBase = new MyBaseClass();
      MyDerivedClass myDerived = new MyDerivedClass();
      object o = myDerived;
      MyBaseClass b = myDerived;
 
      Console.WriteLine("mybase: Type is {0}", myBase.GetType());
      Console.WriteLine("myDerived: Type is {0}", myDerived.GetType());
      Console.WriteLine("object o = myDerived: Type is {0}", o.GetType());
      Console.WriteLine("MyBaseClass b = myDerived: Type is {0}", b.GetType());
   }
}
 
 
/*
 
This code produces the following output.
 
mybase: Type is MyBaseClass
myDerived: Type is MyDerivedClass
object o = myDerived: Type is MyDerivedClass
MyBaseClass b = myDerived: Type is MyDerivedClass 
 
*/

6、ReferenceEquals函数   返回

函数原型:public static bool ReferenceEquals( Object objA,Object objB )

作用:判断两个指定的对象实例是否是同一个实例。从描述也可以看出来,如果参数是值类型,则会装箱,比较的是装箱后的对象实例。

注意:

如果 objA 是与 objB 是相同的实例,或者如果二者都为空引用,则为 true;否则为 false

下面的代码示例使用 ReferenceEquals 确定两个对象是否是相同的实例。

using System;
 
class MyClass {
 
   static void Main() {
      object o = null;
      object p = null;
      object q = new Object();
 
      Console.WriteLine(Object.ReferenceEquals(o, p));
      p = q;
      Console.WriteLine(Object.ReferenceEquals(p, q));
      Console.WriteLine(Object.ReferenceEquals(o, p));
   }
}
 
 
/*
 
This code produces the following output.
 
True
True
False
 
*/

7、MemberWiseClone()函数   返回

函数签名:protected Object MemberWiseClone()

作用:创建当前 Object 的浅表副本。

注意:

MemberwiseClone 方法创建一个浅表副本,方法是创建一个新对象,然后将当前对象的非静态字段复制到该新对象。如果字段是值类型的,则对该字段执行逐位复制。如果字段是引用类型,则复制引用但不复制引用的对象;因此,原始对象及其复本引用同一对象。非静态字段属于整个类,不管是深拷贝,还是浅拷贝,都是不需要处理的。并且需要注意该方法是protected的,即只有在类本身内部,或者它的继承者内部才能够调用它。

例如,考虑一个名为 X 的对象,该对象引用对象 A 和 B。对象 B 又引用对象 C。X 的浅表副本创建一个新对象 X2,该对象也引用对象 A 和 B。与此相对照,X 的深层副本创建一个新对象 X2,该对象引用新对象 A2 和 B2,它们分别是 A 和 B 的副本。B2 又引用新对象 C2,C2 是 C 的副本。使用实现 ICloneable 接口的类执行对象的浅表或深层复制。

下面的代码示例说明如何使用 MemberwiseClone 复制类的实例。

using System;
 
class MyBaseClass {
   public static string CompanyName = "My Company";
   public int age;
   public string name;
}
 
class MyDerivedClass: MyBaseClass {
 
   static void Main() {
 
   // Creates an instance of MyDerivedClass and assign values to its fields.
   MyDerivedClass m1 = new MyDerivedClass();
   m1.age = 42;
   m1.name = "Sam";
 
   // Performs a shallow copy of m1 and assign it to m2.
   MyDerivedClass m2 = (MyDerivedClass) m1.MemberwiseClone();
   }
}

8、ToString()函数

8、ToString()函数   返回

函数签名:public virtual string ToString()

作用:返回一个代表当前对象的字符串

注意:默认情况下返回的是该对象所属类型的全名称。继承类可以重写该方法,以便自定义显示输出内容,如果继承类需要控制更多的格式化输出,则需要实现IFormattable接口。

下面的代码演示了如何重写ToString(),以及如何实现IFormattable接口

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
 
namespace IFormattablePractise
{
    public class Point : IFormattable
    {
        int x;
        int y;
 
        public Point(int x , int y) 
        {
            this.x = x;
            this.y = y;        
        }
 
        public override string ToString()
        {
           return ToString(null, null);
        }
 
        #region IFormattable Members
        public string ToString(string format, IFormatProvider formatProvider)
        {
            // If no format is passed, display like this: (x, y).
            if (format == null) return String.Format("({0}, {1})", x, y);
 
            // For "x" formatting, return just the x value as a string
            if (format == "x") return x.ToString();
 
            // For "y" formatting, return just the y value as a string
            if (format == "y") return y.ToString();
 
            // For any unrecognized format, throw an exception.
            throw new FormatException(String.Format("Invalid format string: '{0}'.", format));
        }
        #endregion
    }
 
    class Program
    {
        static void Main(string[] args)
        {
            // Create the object.
            Point p = new Point(5, 98);
 
            // Test ToString with no formatting.
            Console.WriteLine("This is my point: " + p.ToString());
 
            // Use custom formatting style "x"
            Console.WriteLine("The point's x value is {0:x}", p);
 
            // Use custom formatting style "y"
            Console.WriteLine("The point's y value is {0:y}", p);
 
            Console.WriteLine("my custom point value is ({0:x}:{1:y})",p,p);
 
            try
            {
                // Use an invalid format; FormatException should be thrown here.
                Console.WriteLine("Invalid way to format a point: {0:XYZ}", p);
            }
            catch (FormatException e)
            {
                Console.WriteLine("The last line could not be displayed: {0}", e.Message);
            }
            Console.ReadLine();
        }
    }
}