Note: The C# keyword object is an alias for System.Object class.
System.Object class (or just Object class) is the ultimate base class of all classes in .NET. Object is the root of the type hierarchy even if the type did not explicitly derive from it. And because all classes in the .NET Framework are derived from Object, every method defined in the Object class is available to all objects in the system. Derived class can and do override some of these methods.
The following table presents a quick description of the Object class methods:
| Access | Name | Description |
|
public virtual |
Equals | Determines whether two Object instances are equal. |
| public virtual | GetHashCode | Generates a hash code (most useful in hash tables) |
| public | GetType |
Gets the runtime Type of the current instance. |
|
public static |
ReferenceEquals |
Determines whether the specified Object instances refer to the same instance. |
|
public virtual |
ToString | Returns a human-readable string representation of the object |
| protected | Finalize |
Causes the object to be placed in a finalization queue, such that the Object has chance to clean up resources before the Object is reclaimed by the garbage collector. |
| protected | MemberwiseClone |
Creates a shallow (as opposed to a deep copy) of the Object instance. |
The following sections examine all members of class Object in more detail:
This is a very simple constructor that takes no parameters. The constructor can be used to directly create an instance of class Object, but most often it is called indirectly by constructors in derived classes (recall that a constructor in a derived classes is executed last while a constructor in a base class is executed first). The syntax is very easy:
Object o1 = new Object();
object o2 = new object(); // Same as
above but using the object C# keyword which is really an alias for System.Object
GetHashCode is a virtual method that exists primarily for the benefit of just two types:
System.Collections.Hashtable
System.Collections.Generic.Dictionary<TKey,TValue>
These are hashtables - collections where each element (value) is identified by a key. The key is used for storage and retrieval of its associated element. Rules for overriding object.GetHashCode():
If you override object.Equals, you must override object.GetHashCode.
If you override object.GetHashCode, you must override object.Equals.
object.GetHashCode must return the same on two objects for which object.Equals returns true.
object.GetHashCode must not throw exceptions.
object.GetHashCode must return the same value if called repeatedly on the same object (unless the object has changed).
object.GetHashCode must be overridden for structs as the default for structs performs a bitwise exclusive OR (XOR) on each field in the struct.
Note that if the object's has code changes after the object has been used as a key, the object will no longer be accessible in the dictionary. You can pre-empt this by basing the hashcode on immutable fields. For example, if your object has an ID field that never changes, use that.
There are two types of equality:
Value equality: Two values are equivalent in some way.
By default:
Reference types use referential equality.
C# uses three standard protocols to implement equality:
The == and != operators.
The IEquatable<T> interface (see Interfaces chapter)
Basic rules for operator== :
For predefined value types, operator== uses value equality: returns true if the values of its operands are equal, false otherwise.
For reference types other than string, operator== uses referential equality: returns true if its two operands refer to the same object.
For the string type, operator== compares the values of the strings.
The subtleties with == and != arise because they are static operators, and are statically resolved. When you use == or !=, C# makes a compile-time decision as to which type will perform the comparison, and no virtual behaviour comes into play.
public static void TestValueEquality1() { // Compiler hard-wires == to the int type because x and y are both of type int int x = 10; int y = 10; bool isEqual = x == y; // true. value type, hence value equality isEqual = object.ReferenceEquals(x, y); // false. different objects due to boxing // DateTime is also a value type (struct). Note that a struct's Equals applies // structural value equality by default, i.e., two structs are equal if all their // members are equal. var dt1 = new DateTime(2012, 10, 1, 0, 0, 0); var dt2 = new DateTime(2012, 10, 1, 0, 0, 0); isEqual = dt1 == dt2; // true. value type, hence value equality // (overloaded operator==) isEqual = object.ReferenceEquals(dt1, dt2); // false. different objects due to boxing }
public static void TestReferentialEquality1() { // Compiler hard-wires == to the object type because a and b are both of type object // object.operator== uses referential equality to compare a and b. a and b both refer // to different objects. object a = 5; object b = 5; var isEqual = a == b; // false. Reference types, hence referential // equality: different objects (but same value) isEqual = object.ReferenceEquals(a, b); // false. different objects var foo1 = new Foo() {X = 10}; var foo2 = new Foo() {X = 10}; isEqual = foo1 == foo2; // false. Reference types, hence referential // equality: different objects (but same value) isEqual = object.ReferenceEquals(foo1, foo2); // false. different objects var foo3 = foo1; isEqual = foo1 == foo3; // true. Reference types, hence referential // equality: same objects isEqual = object.ReferenceEquals(foo1, foo3); // true. same objects }
First note the following:
ValueType.Equals: the default implementation uses reflection to compare the corresponding fields of the input instance and this instance
Object.Equals: The default implementation uses referential equality, i.e., whether two object instances refer to the same memory location
The virtual object.Equals method is resolved at runtime according to the object's actual type.
// Disassembled code .... public struct Int32 : IComparable, IFormattable, IConvertible, IComparable<int>, IEquatable<int> { public override bool Equals(object obj) { return obj is int && this == (int)obj; } public bool Equals(int obj) { return this == obj; } ... } // Disassembled code .... public struct DateTime : IComparable, IFormattable, IConvertible, ISerializable, IComparable<DateTime>, IEquatable<DateTime> { public override bool Equals(object value) { return value is DateTime && this.InternalTicks == ((DateTime)value).InternalTicks; } public bool Equals(DateTime value) { return this.InternalTicks == value.InternalTicks; } ... } public static void TestValueEquality2() { // Compiler hard-wires == to the int type because x and y are both of type int int x = 10; int y = 10; var isEqual = x.Equals(y); // true. calls Int32.Equals(Int32) - IEquatable implementation isEqual = x.Equals((object)y); // true. calls Int32.Equals(object) - overrides object.Equals(): // true if argument is an instance of Int32 and equals the value // of this instance isEqual = object.Equals(x, y); // true. // DateTime is also a value type var dt1 = new DateTime(2012, 10, 1, 0, 0, 0); var dt2 = new DateTime(2012, 10, 1, 0, 0, 0); isEqual = dt1.Equals(dt2); // true. calls DateTime.Equals(DateTime) - IEquatable implementation isEqual = dt1.Equals((object)dt2); // true. calls DateTime.Equals(object) - overrides // object.Equals(): true if argument is an instance of // DateTime and equals the value of this instance; isEqual = object.Equals(dt1, dt2); // true. }
public static void TestReferentialEquality2() { object a = 5; // run-time type is int object b = 5; // run-time type is int var isEqual = a.Equals(b); // true. Virtual Equals calls Int32.Equals(object) isEqual = object.Equals(a, b); // true. Calls a.Equals(b). See line above var foo1 = new Foo() { X = 10 }; // run-time type is Foo var foo2 = new Foo() { X = 10 }; // run-time type is Foo isEqual = foo1.Equals(foo2); // false. Virtual equals calls Foo.Equals(object). // Since Foo does not override Equals(), we get referential // equality: foo1 and foo2 are different isEqual = object.Equals(foo1, foo2); // false. Calls foo1.Equals(foo2). See line above var foo3 = foo1; isEqual = foo1.Equals(foo3); // true. Virtual equals calls Foo.Equals(object). Since // Foo does not override Equals(), we get referential // equality: foo1 and foo3 are the same isEqual = object.Equals(foo1, foo3); // true. Calls foo1.Equals(foo3). See line above }
Sometime it makes sense to override the equality behaviour when writing a type. There are two cases for doing so:
Changing the meaning of equality: This makes sense when the default behaviour of == and Equals is unnatural for your type. Recall:
For value types: the == operator uses value equality, and the Equals override uses value equality (see override for Int32.Equals)
For reference types: the == operator uses reference equality,
and the default Equals uses reference equality.
Speeding the equality comparison with structs: The default structural equality is slow. Taking over by overriding Equals improves performance. Further overloading the == operator and implementing IEquatable<T> allows unboxed equality comparisons.
To override equality semantics:
Override GetHashCode() and Equals().
Optionally overload == and != operators.
Optionally implement IEquatable<T>.
Set GetHashCode() Method section.
If you implement IEquatable<T>.Equals, you should also override the base class implementations of Object.Equals(Object) and GetHashCode so that their behaviour is consistent with that of the IEquatable<T>.Equals method
See IEquatable<> interface section.
See rules for overriding Equals.
Rules for overriding the virtual Equals
For value types:
Always override virtual Equals.
Always implement the GetHashCode method.
Always overload == and != operators.
For Reference types:
Generally, do not overload == and != operators. This ensures the default implementation uses referential equality.
Overload the == operator if your type is a base type such as a Point, String, BigNumber, where the user never wants referential equality. Any time you consider overloading the addition (+) and subtraction (-) operators, you also should consider overloading the == operator.
If you implement the == operator, override Equals make them do the same thing.
Implement the GetHashCode method whenever you implement the Equals method.
Override the Equals method any time you implement the IComparable.
The following statements must be true for all implementations of the Equals method (in the following , x, y, and z are non-null objects):
x.Equals(x) is true except for floating point numbers.
x.Equals(y) returns true if both x and y are NaNs.
x.Equals(null) returns false.
Implementations of Equals should not throw exceptions.
The Equals method for class String has been overridden to check for value equality (i.e., both strings contain the same data).
Recall that virtual Equals and operator== both use referential equality on reference types. However, a custom reference type can override Equals to perform value equality while leaving the operator== to perform referential equality, or could overload operator== to perform value equality as well. The following example illustrates:
class Person : IEquatable<Person> { // Fields const int prime = 397; // Properties public string Name { get; private set; } public int Age { get; private set; } // Constructors public Person(string name, int age) { Name = name; Age = age; } // Determine whether the value of the current object is equal to the given object public override bool Equals(object other) { if (!(other is Person)) return false; return Equals((Person) other); /* The logic below is used if IEquatable<T> was not implemented // First step in checking for equality is ensuring that the right-hand-side (RHS) // object is not null if (other == null) return false; // Second step in checking for equality is ensuring that both objects are of the same type. // Note the following important points: // 1. We use GetType to determine whether the run-time type of both objects is the // same. // 2. typeof is not used as it returns the compile-time (or static type) of the object. // 3. The is operator (other is Person) is not used as the check would return true in cases // where other was an instance derived from class Person. if (GetType() != other.GetType()) return false; // Having verified that other has the same exact type as this instance, we cast other to Person // and return the result of comparing the values of these two objects. As class Person has only // two fields, equality is established if both objects have same values for both name and dob Person pTarget = (Person)other; return ((this.Name == pTarget.Name) && (this.Age == pTarget.Age)); */ } public override int GetHashCode() { // This is a pretty standard pattern for GetHashCode(). Note the use of the bitwise XOR (^) // operator, a prime number, and the conditional operator to handle nulls in GetHashCode (if // the Name property is null, it XORs the result with zero, which just means it doesn't // change the result at all) return (Age * prime) ^ (Name != null ? Name.GetHashCode() : 0); } public bool Equals(Person other) { if (ReferenceEquals(other, this)) return true; if (ReferenceEquals(other, null)) return false; return Age == other.Age && Name.Equals(other.Name, StringComparison.OrdinalIgnoreCase); } public static bool operator == (Person lhs, Person rhs) { // If we used == to check for null instead of Object.ReferenceEquals(), we'd get a // StackOverflowException because the call would be recursive! if (ReferenceEquals(lhs, null)) return false; // Check lhs is not null return lhs.Equals(rhs); } public static bool operator !=(Person lhs, Person rhs) { return !(lhs == rhs); } }
static public void TestPerson1() { Person p1 = new Person("yazan", 40); Person p2 = new Person("omar", 35); Person p3 = new Person("omar", 35); var isEqual = p2.Equals(p3); // true isEqual = p2.Equals((object) p3); // true; isEqual = object.Equals(p2, p3); // true. Calls object.Equals isEqual = p2 == p3; // true! OVerloaded == operator to enforce value equality isEqual = ReferenceEquals(p2, p3); // false var persons = new List<Person> {p1, p2, p3}; var contains = persons.Contains(p1); // true }
The following example provides an example on overriding the Equals method for value types:
struct Area : IEquatable<Area> { public int Width { get; private set; } public int Height { get; private set; } public Area(int width, int height) : this() { Width = width; Height = height; } public bool Equals(Area rhs) { return Width*Height == rhs.Width*rhs.Height; } public override bool Equals(object rhs) { if (!(rhs is Area)) return false; // Delegate to IEquatable<T> implementation return Equals((Area) rhs); } public override int GetHashCode() { return Width ^ Height; } public static bool operator == (Area lhs, Area rhs) { // Delegate to IEquatable<T> implementation return lhs.Equals(rhs); } public static bool operator != (Area rhs, Area lhs) { // Delegate to IEquatable<T> implementation return !lhs.Equals(rhs); } }
public static void TestArea() { var area1 = new Area(4, 8); var area2 = new Area(8, 4); var isEqual = area1 == area2; // true. Calls IEquatable<Area> implementation isEqual = area1.Equals(area2); // true. Calls IEquatable<Area> implementation isEqual = area1.Equals((object)area2); // true. Calls IEquatable<Area> implementation }
GetType returns the exact run-time type of the current instance. Compare this to typeof which returns the static type of the given class. The following example illustrates:
private static void TestGetType()
{
Person p = new Person("yazan", DateTime.Parse("01/01/2006"));
Employee e = new Employee( "omar", DateTime.Parse(
"02/02/2006"), 10);
object o = e;
Person p2 = new Employee("someone", DateTime.Parse("05/05/2005"),
100);
Trace.WriteLine("p.GetType()
full name: " + p.GetType().FullName);
Trace.WriteLine("e.GetType()
full name: " + e.GetType().FullName);
Trace.WriteLine("o.GetType()
full name: " + o.GetType().FullName);
Trace.WriteLine("p2.GetType() full
name: " + p2.GetType().FullName);
Trace.WriteLine("typeof(Person) full name: " +
typeof(Person).FullName);
Trace.WriteLine("typeof(Employee) full name: " +
typeof(Employee).FullName);
Trace.WriteLine("typeof(object) full name: " +
typeof(object).FullName);
Trace.WriteLine("typeof(Person) full name: " +
typeof(Person).FullName);
}
Output:
p.GetType() full
name: Objects.Person
// p static type is Person. Run-time type is also Person
e.GetType() full name: Objects.Employee
// e static type is Employee. Run-time type is also
Employee
o.GetType() full name: Objects.Employee
// o static type is object, but run-time type is Employee
p2.GetType() full name: Objects.Employee
// p2 static type is Person, but run-time type is Employee
typeof(Person) full name: Objects.Person
// static type of p is Person
typeof(Employee) full name: Objects.Employee
// static type of e is Employee
typeof(object) full name: System.Object
// static type of o is object
typeof(Person) full name: Objects.Person
// static type of p2 is Person
public virtual string ToString()
This method should be overridden in derived classes when it is required or beneficial to return the culture-specific appropriate textual representation of the object. For example, for an Employee object, the ToString() override might return a formatted string that fully describes the employee, whereas for a data type such as Int32, the ToString() override should return the integer value as a string. The default implementation returns the fully-qualified name of the object. The following example illustrates:
internal class Employee : Person
{
private int nEmployeeID;
public Employee(string name, DateTime dob, int nID) :
base(name, dob)
{
nEmployeeID = nID;
}
...
public override string ToString()
{
return "(Name, " + base.Name + "),
(DOB, " + base.DOB + ")";
}
}
private static void TestToString()
{
Address a = new Address();
string s1 = a.ToString();
// Default implementation returns "Objects.Address"
Employee e = new Employee("yazan", DateTime.Parse("01/01/2000"),
1);
string s2 = e.ToString();
// "(Name, yazan), (DOB, 01/01/2000 00:00:00)"
double d = 12345.00;
string s3 = d.ToString();
// "12345"
}
Derived classes that require more control over the formatting of strings than ToString provides must implement IFormattable whose ToString method uses the current thread's CurrentCulture property.
// Actual implementation
(disassembled code)
public static bool ReferenceEquals(object objA, object objB) // Note:
non-virtual
{
return objA == objB;
}
This method determines whether the given objects, objA and objB, refer to the same instance. ReferenceEquals and the default implementation of Equals are therefore, the same. Recall that the default implementation of Equals tests for reference equality. However, Equals is virtual and can be overridden in derived classes to test for value equality instead. Further, it is possible for a class to overload operator== so that objA == objB would also return true. In such cases, calling ReferenceEquals guarantees normal referential equality. The following example illustrates:
private static void TestReferenceEquals()
{
Person p1 = new Person("yazan", DateTime.Parse(
"01/01/2000"));
Person p2 = new Person("omar", DateTime.Parse(
"01/01/2006"));
bool b1 = Person.ReferenceEquals( p1, p2 );
// false. Different instances
Person p3 = p1;
bool b2 = Person.ReferenceEquals( p1, p3 );
// true. p1 and p3 point to the same memory location
Person p4 = null;
Person p5 = null;
bool b3 = Person.ReferenceEquals( p4, p5 );
// true
}
protected object MemberwiseClone() // Note: non-virtual
Recall that a 'shallow copy' and a 'deep copy' both create a new object that points to a memory location that is different from that of the original object. The contents of this new memory location are the same as the contents of the source memory location. Shallow and deep copies only apply to reference types (value types directly contain their data and copying value types always creates new copies). For a shallow copy data members of the new object that are reference types still point to the original memory location but data members of the new object that are value types are copied bit-by-bit. For a deep copy, all data members, reference and value types, point to different memory locations.
Object.MemberwiseClone creates a shallow copy of an object, and then copies the non-static fields of the source object to the new object. If a field is a value type, a bit-by-bit copy is performed. If a field is a reference type, the reference (address) is copied and the newly copied field continues to point to the source memory location. To allow a class to perform deep-copies, implement ICloneable.
For example, assume an object A that uses objects B and C. Object B in turn uses object D:
public class A
{
private B obB;
private C obC;
}
public class B
{
private D obD;
}
public class C
{
private int nNum;
}
A shallow copy of A creates a new object A2 that also references objects B and C. In contrast, a deep copy of A creates a new object A2 that references the new objects B2 and C2 and B2 in turn references the new object D2 which is a copy of object D. The following example illustrates:
class Address
{
private string strStreet;
private int nHouseNumber;
public string Street
{
get { return strStreet; }
set { strStreet = value; }
}
public int HouseNumber
{
get { return nHouseNumber; }
set { nHouseNumber = value; }
}
}
class Student
{
// Data members
private int m_nID;
private Address m_obAddress;
// Constructors
public Student(int id, string street, int houseno)
{
m_obAddress = new Address();
m_obAddress.HouseNumber = houseno;
m_obAddress.Street = street;
m_nID = id;
}
// Operations
public Student GetShallowCopy()
{
//
MemberwiseClone is protected and can only be called from base or derived classes
return (Student)this.MemberwiseClone();
}
// Properties
public Address Address
{
get { return m_obAddress; }
}
public int ID
{
get { return m_nID; }
}
}
private static void TestMemberwiseClone()
{
// Create an object and retrieve its
data members
Student s1 = new Student(1234, "DotNet Blvd", 4321);
Address a1 = s1.Address;
int n1 = s1.ID;
// Now create a copy of object s1
Student s2 = s1.GetShallowCopy();
// Uses MemberwiseClone()
Address a2 = s2.Address;
int n2 = s2.ID;
// Now that we have original and copy,
let's compare them
// Do s1 and s2 point to the same memory location? Equals was
not overridden, hence
// it uses reference equality
bool b1 = s1.Equals(s2);
// false:
s1 and s2 do not point to the same memory location
bool b2 = object.ReferenceEquals(s1, s2);
// false:
s1 and s2 do not point to the same memory location
// Do a1 and a2 point to the same
memory location ?
bool b3 = a1.Equals(a1);
// true:
a1 and a2 point to the same memory location
bool b4 = n1.Equals(n2);
// true:
both values are equal
}
For the majority of the objects that your application uses, you can rely on the garbage collector to implicitly perform all memory management tasks (i.e., cleanup). However, when you create objects that encapsulate unmanaged resources, you must explicitly release the unmanaged resources when you are done using them. The most common type of an unmanaged resource is an object that wraps an OS resource such as a file, window, or network connection. Although the garbage collector is able to track an object that encapsulates an unmanaged resource, it does not have specific knowledge about how to clean up memory used by the unmanaged resource. For an object that uses or wraps unmanaged resources, you have to use object.Finalize to allow the object to clean up its unmanaged resources when the garbage collector reclaims the memory used by the object.
By default, object.Finalize does nothing. Therefore, if you want the garbage collector to perform cleanup operations on your object before it reclaims the object's memory, you must override object.Finalize method in your class. In C#, object.Finalize cannot be overridden and cannot be called, instead it is represented in C# with the destructor syntax. The syntax is convenient because it implicitly calls the Finalize method for an object's base class This guarantees that Finalize is called for all levels of destructors from which the current class is derived. For example:
~MyClass()
{
// Clean up un-managed resources
}
Translates to:
protected override void Finalize()
{
try
{
// Clean up
un-managed resources
}
finally
{
base.Finalize();
}
}
The garbage collector keeps track of objects that have override the Finalize method (objects that have destructors in C#) using an internal structure called the finalization queue. Each time your application creates an object that has a Finalize method (destructor in C#), the garbage collector places an entry for that object in the finalization queue. The finalization queue contains entries for all managed objects that need to have their finalization code (destructors in C#) called before the garbage collector can reclaim their memory.
Implementing Finalize can have a negative impact on performance and you should avoid using them when possible. Reclaiming memory used by objects with Finalize methods requires at least two garbage collections: In the first pass when the garbage collector performs a collection it reclaims the memory for unreachable objects without finalizers. At this time it cannot collect the unreachable objects that do have finalizers. Instead it removed the entries for these objects from the finalization queue and places them in a list of objects marked as ready for finalization. Entries in this list point to managed objects that are ready to have their finalization code called. The garbage collector calls the Finalize method (destructor in C#) for these objects and then removes the entries from the list. In the second pass, the garbage collector will determine that the finalized objects are truly garbage because they are no longer pointed to by the entries in the list of objects marked ready for finalization. In this second pass, the object's memory is actually reclaimed.
To properly dispose of unmanaged resources, it is recommended that you implement the IDisposable interface. IDisposable.Dispose should be called directly to free unmanaged resources. A type's IDisposable.Dispose should release all resources it owns and all resources owned by its base types by calling its parent Dispose method. The parent type's in turn releases all its resources and in turn calls its parent type's Dispose and so on by propagating this pattern across the hierarchy of base types. For proper cleanup, a Dispose method should be safely callable multiple times without throwing an exception.
After Dispose cleans up relevant resources, it should also call GC.SuppressFinalize to prevent the object's Finalize (destructor in C#) from being called. Recall that the finalizer (destructor in C#) was added to safeguard against not calling Dispose directly.
To properly clean up resources the following pattern is used (throughout .NET Framework):
Note on thread safety: The protected virtual bool Dispose(bool bDisposing) method does not enforce thread safety because the method cannot be called from a user thread and a finalizer thread at the same time. In addition, a client application using the MyBaseResource class should never allow multiple user threads to call this protected Dispose method at the same time. An application or class library should be designed to allow only one thread to own the lifetime of a resource and to call Dispose when the resource is no longer needed.
The following program illustrates these concepts:
// Cleanup design pattern for base classes
public class MyBaseResource : IDisposable
{
/* Data members */
bool bDisposed = false;
/* Constructors/Destructors */
// Use C# destructor for finalization code. This destructor will only run if Dispose()
// was not called (Dispose if called calls GC.SuppressFinalize)
~MyBaseResource()
{
Trace.WriteLine( "MyBaseResource.~MyBaseResource" );
Dispose(false);
}
/* Public interface */
public void DoSomething()
{
Trace.WriteLine( "DoSomething" );
}
/* Interface methods */
// IDisposable.Dispose() implementation. This method should not be made override to
// prevent derived classes from overriding
public void Dispose()
{
Trace.WriteLine( "MyBaseResource.Dispose()" );
// delegate to another method
Dispose( true );
// Then remove this object form the finalization queue
GC.SuppressFinalize( this );
}
/* Implementation details */
// Performs actual clean up. If bDisposing is true, Dispose has been called by the
// user and both managed and unmanaged resources must be freed. If bDisposing is false,
// this method has been called by the finalization code and it should release only
// unmanaged resources
protected virtual void Dispose( bool bDisposing )
{
Trace.WriteLine( "MyBaseResource.Dispose(" + bDisposing + ")" );
if (bDisposed)
return;
Trace.Indent();
if (bDisposing == true)
{
Trace.WriteLine("Freeing managed resources");
// Dispose all managed and unmanaged
resources.
SomeComponent.Dispose(); // Disposing a managed resource
CloseHandle( hWnd );
// Disposing an unmanaged resource
}
else
{
Trace.WriteLine("Freeing unmanaged resources");
CloseHandle( hWnd );
// Disposing an unmanaged resource
}
bDisposed = true;
}
}
// Cleanup design pattern for derived classes. Note that this class does not have
// a Finalize method (destructor) because the base class's Finalize method will call
// the derived class's Dispose(bDisposing). This class does not also have an
// IDisposable.Dispose implementation because it inherits them from the base class
public class MyDerivedResource : MyBaseResource
{
/* Data members */
bool bDisposed = false;
/* Public interface */
public void foo()
{
Trace.WriteLine( "MyDerivedResource.foo()" );
// Allocates an unmanaged resource
}
/* Inherited implementation detail */
protected override void Dispose( bool bDisposing )
{
Trace.WriteLine( "MyDerivedResource.Dispose(" + bDisposing + ")" );
if (bDisposed)
return;
Trace.Indent();
try
{
if (bDisposing == true)
{
Trace.WriteLine("Freeing managed resources");
// Dispose all managed and unmanaged resources.
SomeComponent.Dispose(); // Disposing a managed resource
CloseHandle( hWnd );
// Disposing an unmanaged resource
}
else
{
Trace.WriteLine("Freeing unmanaged resources");
CloseHandle( hWnd );
// Disposing an unmanaged resource
}
bDisposed = true;
}
finally
{
// Now call the base class Dispose method
base.Dispose(bDisposing);
}
}
}
The following code creates a derived class that allocates an unmanaged resource but 'forgets' to explicitly call Dispose. Note in the output window how the finalization code takes care of freeing unmanaged resources:
private void btnBasics_Click(object sender, System.EventArgs e)
{
// Allocate a class that uses some unmanaged resource
MyDerivedResource obD = new MyDerivedResource();
// Do something with the unmanaged resource
obD.foo();
}

The following code creates the same derived class that allocates an unmanaged resource and explicitly calls Dispose. Now note in the output window that the finalization code does not get executed:
private void btnBasics_Click(object sender, System.EventArgs e)
{
// Allocate a class that uses some unmanaged resource
MyDerivedResource obD = new MyDerivedResource();
// Do something with the unmanaged resource
obD.foo();
// Dispose of managed and unmanaged
resources
obD.Dispose();
}

The following points summarize most of the points above:
You should not implement Finalize for managed objects (garbage collector cleans up managed resources automatically)
You should only implement Finalize to clean up unmanaged resources.
When implementing Finalize to clean up unmanaged resources, it should be done in conjunction with IDisposable. The Finalize method then becomes a safeguard to clean up resources in the event that Dispose was not called directly by the client.
By default, object.Finalize does nothing. You must override this method in your class to allow the garbage collector to clean up unmanaged resources.
You cannot override the Finalize method in C#. You have to use the destructor syntax.
The Finalize method (destructor in C#) should not call any method on any object other than that of its base class. This is because the other objects being called could be collected at the same time as the calling object, such as in the case of CLR shutdown.
If you allow any exceptions to escape the Finalize method, the system assumes that the Finalize method returned and continues calling the Finalize methods of other objects.
The exact time when Finalize runs during garbage collection is undefined. Resources are not guaranteed to be released at any specific time unless calling the Dispose method.
The finalizers of two objects are not guaranteed to run in any specific order even if one object refers to the other.
The thread on which the finalizer is run is unspecified.
Because the CLR attempts to run finalizers to completion, other finalizers might not be called if a finalizer blocks indefinitely.
When you use an object that encapsulates resources, you should make sure that the object's Dispose method gets called when you are done using the object. In C# you can do using either the using statement of the try-finally block.
using using
The using statement is only useful for an object whose lifetime does
not extend beyond the method in which the object was constructed.
using try-finally
Since using is a C# keyword, the
alternative to using using in languages other than
C# is the try-finally block.
The last example can be written more elegantly as follows:
// Approach 1 to properly clean up
object that encapsulate resources.
private void btnBasics_Click(object sender, System.EventArgs e)
{
// Allocate a class that uses some unmanaged resource
using (MyDerivedResource obD = new
MyDerivedResource())
{
// Do something with the unmanaged resource
obD.foo();
} // IDispose.Dispose
called here
}
// Approach 2 to properly clean up
object that encapsulate resources
private void btnBasics_Click(object sender, System.EventArgs e)
{
MyDerivedResource obD;
try
{
// Allocate a class that uses some unmanaged resource
obD = new MyDerivedResource());
// Do something with the unmanaged resource
obD.foo();
}
finally
{
obD.Dispose();
}
}