Chapter 3. Programming the .NET Framework

Most modern programming languages include some form of runtime that provides common services and access to the underlying operating systems and hardware. Examples of this range from a simple functional library, such as the ANSI C Runtime used by C and C++, to the rich object-oriented class libraries provided by the Java Runtime Environment.

Similar to the way that Java programs depend on the Java class libraries and virtual machine, C# programs depend on the services in the .NET Framework such as the base class libraries (BCL) and the Common Language Runtime (CLR).

For a high-level overview of the BCL, see Chapter 4.

This chapter addresses the most common tasks you need to perform when building C# programs. These topics generally fall into one of two categories: leveraging functionality included in the BCL and interacting with elements of the CLR.

Common Types

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

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

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( ) {...}
}
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 compares the objects by reference, so classes are expected to override this method to compare two objects by value.

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

GetHashCode( )

This method allows objects to provide their own hash function for use in collections.

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 actually doesn’t meet these criteria, as it merely returns a number based on the object reference. For this reason, you should usually override this method in your own types.

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 reflection in general, see Section 3.10.

ToString( )

This method provides a string representation of the object and is generally intended for use when debugging.

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 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.

Creating BCL-friendly types

When defining new types that work well with the rest of the BCL, you should override several of these methods as appropriate. Some of these overrides have parallels with C# operators that can also be overridden where it makes sense.

Here is an example of a new value type that is intended to be a good citizen in the BCL:

public class Point3D {
  public 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 is Point3D)) // Check for type equivalence
      return false;
    return (this==(Point3D)o); // Implemented by operator==
  }
  public static bool operator !=(Point3D lhs, Point3D rhs) {
    return (!(lhs==rhs)); // Implemented by operator==
  }
  public static bool operator ==(Point3D lhs, Point3D rhs) {
    return ((rhs.x==lhs.x) && (rhs.y==lhs.y) && (rhs.z==lhs.z));
  }
  public override int GetHashCode( ){
    return x^y^z;
  }
  public override string ToString( ) {
    return String.Format("[{0},{1},{2}]", x, y, z);
  }
}

This class overrides Equals , operator== , and operator!= 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:

using System;
using System.Collections;
public class Point3D {...}
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, == & !=
    int i = 100;
    Console.WriteLine(p1.Equals(i)); // Prints "False"
    Console.WriteLine(p1==p2); // Prints "False"
    Console.WriteLine(p2==p3); // Prints "True"

    // Use a hashtable to store points (uses GetHashCode)
    Hashtable ht = new Hashtable( );
    ht["p1"] = p1; 
    ht["p2"] = p2;
    ht["p3"] = p3;

    // Prints "p2=[2,2,2] p3=[2,2,2] p1=[1,1,1]"
    foreach (DictionaryEntry de in ht)
      Console.Write("{0}={1} ", de.Key, de.Value);  
  }
}

ICloneable Interface

public interface ICloneable {
  object Clone( );
}

ICloneable allows classes or structs 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 either simply return this.MemberwiseClone( ), which performs a shallow copy (the fields are copied directly), or you can perform a custom deep copy, where you clone individual fields in the class or struct. The following example is the simplest implementation ICloneable:

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

IComparable Interface

interface IComparable {
  int CompareTo(object o);
}

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

  • Returns - if instance < o

  • Returns + if instance > o

  • Returns 0 if instance == o

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);
   }
}

IFormattable Interface

public interface IFormattable {
  string Format(string format, IServiceObjectProvider sop);
}

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 a string which uses a comma for a decimal point. The formatting options are specified by the format string (see Section 3.3.4). If an IServiceObjectProvider 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 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.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.117.230.81