only for RuBoard - do not distribute or recompile Previous Section Next Section

3.1 Common Types

Certain types in the FCL are ubiquitous, in that they are fundamental to the way the FCL and CLR work and provide common functionality used throughout the entire FCL.

This section identifies some of the most common of these types and provides guidelines to their usage. The types mentioned in this section all exist in the System namespace.

3.1.1 Object Class

The System.Object class is the root of the class hierarchy and serves as the base class for every other class. The C# object type aliases System.Object. System.Object provides a handful of useful methods that are present on all objects, and whose signatures are listed in the following fragment of the System.Object class definition:

public class Object {
   public Object(  ) {...}
   public virtual bool Equals(object o) {...}
   public virtual int GetHashCode(  ){...}
   public Type GetType(  ){...}
   public virtual string ToString(  ) {...}
   protected virtual void Finalize(  ) {...}
   protected object MemberwiseClone(  ) {...}
   public static bool Equals (object a, object b) {...}
   public static bool ReferenceEquals (object a, object b) {...}
}
Object( )

The constructor for the Object base class.

Equals(object o)

This method evaluates whether two objects are equivalent.

The default implementation of this method on reference types compares the objects by reference, so classes are expected to override this method to compare two objects by value.

In C#, you can also overload the == and != operators. For more information see Section 2.9.8.1 in the Section 2.9 section in Chapter 2.

GetHashCode( )

This method allows types to hash their instances. A hashcode is an integer value that provides a "pretty good" unique ID for an object. If two objects hash to the same value, there's a good chance that they are also equal. If they don't hash to the same value, they are definitely not equal.

The hashcode is used when you store a key in a dictionary or hashtable collection, but you can also use it to optimize Equals( ) by comparing hash codes and skipping comparisons of values that are obviously not equal. This is a gain only when it is cheaper to create the hashcode than perform an equality comparison. If your hashcode is based on immutable data members, you can make this a no-brainer by caching the hash code the first time it is computed.

The return value from this function should pass the following tests: (1) two objects representing the same value should return the same hashcode, and (2) the returned values should generate a random distribution at runtime.

The default implementation of GetHashCode doesn't actually meet these criteria because it merely returns a number based on the object reference. For this reason, you should usually override this method in your own types.

An implementation of GetHashCode could simply add or multiply all the data members together. You can achieve a more random distribution of hash codes by combining each member with a prime number (see Example 3-1).

To learn more about how the hashcode is used by the predefined collection classes, see Section 3.4 later in this chapter.

GetType( )

This method provides access to the Type object representing the type of the object and should never be implemented by your types. To learn more about the Type object and reflectionin general, see the later section Section 3.10.

ToString( )

This method provides a string representation of the object and is generally intended for use when debugging or producing human-readable output.

The default implementation of this method merely returns the name of the type and should be overridden in your own types to return a meaningful string representation of the object. The predefined types such as int and string all override this method to return the value, as follows:

using System;
class Beeblebrox {}
class Test {
  static void Main(  ) {
    string s = "Zaphod";
    Beeblebrox b = new Beeblebrox(  );
    Console.WriteLine(s); // Prints "Zaphod"
    Console.WriteLine(b); // Prints "Beeblebrox"
  }
}
Finalize( )

The Finalize method cleans up nonmemory resources and is usually called by the garbage collector before reclaiming the memory for the object. The Finalize method can be overridden on any reference type, but this should be done only in a very few cases. For a discussion of finalizers and the garbage collector, see the later section Section 3.12.

MemberwiseClone( )

This method creates shallow copies of the object and should never be implemented by your types. To learn how to control shallow/deep copy semantics on your own types, see Section 3.1.2 later in this chapter

Equals/ReferenceEquals (object   a, object   b)  

Equals tests for value quality, and ReferenceEquals tests for reference equality. Equals basically calls the instance Equals method on the first object, using the second object as a parameter. In the case that both object references are null, it returns true, and in the case that only one reference is null, it returns false. The ReferenceEquals method returns true if both object references point to the same object or if both object references are null.

3.1.1.1 Creating FCL-friendly types

When defining new types that work well with the rest of the FCL, you should override several of these methods as appropriate.

Example 3-1 is an example of a new value type that is intended to be a good citizen in the FCL:

Example 3-1. Defining new value type
// Point3D - a 3D point
// Compile with: csc /t:library Point3D.cs
using System;
public sealed class Point3D {
  int x, y, z;
  public Point3D(int x, int y, int z) {
    this.x=x; this.y=y; this.z=z; // Initialize data
  }
  public override bool Equals(object o) {
    if (o == (object) this) return true; // Identity test
    if (o == null) return false; // Safety test
    if (!(o is Point3D)) // Type equivalence test
      return false;
    Point3D p = (Point3D) o;
    return ((this.x==p.x) && (this.y==p.y) && (this.z==p.z));
  }
  public override int GetHashCode(  ){
    return ((((37+x)*37)+y)*37)+z; // :-)
  }
  public override string ToString(  ) {
    return String.Format("[{0},{1},{2}]", x, y, z);
  }
}

This class overrides Equals to provide value-based equality semantics, creates a hashcode that follows the rules described in the preceding section, and overrides ToString for easy debugging. It can be used as follows:

// TestPoint3D - test the 3D point type
// Compile with: csc /r:Point3D.dll TestPoint3D.cs
using System;
using System.Collections;
class TestPoint3D {
  static void Main(  ) {
    // Uses ToString, prints "p1=[1,1,1] p2=[2,2,2] p3=[2,2,2]"
    Point3D p1 = new Point3D(1,1,1);
    Point3D p2 = new Point3D(2,2,2);
    Point3D p3 = new Point3D(2,2,2);
    Console.WriteLine("p1={0} p2={1} p3={2}", p1, p2, p3);

    // Tests for equality to demonstrate Equals
    Console.WriteLine(Equals(p1, p2)); // Prints "False"
    Console.WriteLine(Equals(p2, p3)); // Prints "True"

    // Use a hashtable to cache each point's variable name
    // (uses GetHashCode).
    Hashtable ht = new Hashtable(  );
    ht[p1] = "p1"; 
    ht[p2] = "p2";
    ht[p3] = "p3"; // replaces ht[p2], since p2 == p3

    // Prints:
    //    p1 is at [1,1,1]
    //    p3 is at [2,2,2] 
    foreach (DictionaryEntry de in ht)
      Console.WriteLine("{0} is at {1} ", de.Value, de.Key);
  }
}

3.1.2 ICloneable Interface

public interface ICloneable {
  object Clone(  );
}

ICloneable allows class or struct instances to be cloned. It contains a single method named Clone that returns a copy of the instance. When implementing this interface your Clone method can simply return this.MemberwiseClone( ), which performs a shallow copy (the fields are copied directly), or you can perform a custom deep copy, in which you clone individual fields in the class or struct.The following example is the simplest implementation of ICloneable:

public class Foo : ICloneable {
      public object Clone(  ) {
       return this.MemberwiseClone(  );
   }
}

3.1.3 IComparable Interface

interface IComparable {
  int CompareTo(object o);
}

IComparable is implemented by types that have instances that can be ordered (see the later section Section 3.4). It contains a single method named CompareTo that:

This interface is implemented by all numeric types: string, DateTime, etc. It may also be implemented by custom classes or structs to provide comparison semantics. For example:

using System;
using System.Collections;
class MyType : IComparable {
  public int x;
  public MyType(int x) {
    this.x = x;
  }
  public int CompareTo(object o) {
    return x -((MyType)o).x;
  }
}
class Test {
  static void Main(  ) {
    ArrayList a = new ArrayList(  );
    a.Add(new MyType(42));
    a.Add(new MyType(17));
    a.Sort(  );
    foreach(MyType t in a)
      Console.WriteLine(((MyType)t).x);
   }
}

3.1.4 IFormattable Interface

public interface IFormattable {
  string ToString(string format, IFormatProvider formatProvider);
}

The IFormattable interface is implemented by types that have formatting options for converting their value to a string representation. For instance, a decimal may be converted to a string representing currency or to a string that uses a comma for a decimal point. The formatting options are specified by the format string (see Section 3.3.4 later in this chapter). If an IFormatProvider interface is supplied, it specifies the specific culture to be used for the conversion.

IFormattable is commonly used when calling one of the String class Format methods (see the later section Section 3.3).

All the common types (int, string, DateTime, etc.) implement this interface, and you should implement it on your own types if you want them to be fully supported by the String class when formatting.

only for RuBoard - do not distribute or recompile Previous Section Next Section