C# Day 2 – Objects and Types (part 4)
The Object Class
As indicated earlier, all .NET classes are ultimately derived from System.Object . In fact, if you don ’ t specify a base class when you define a class, the compiler will automatically assume that it derives from Object . Because inheritance has not been used in this chapter, every class you have seen here is actually derived from System.Object . (As noted earlier, for structs this derivation is indirect: A struct is always derived from System.ValueType , which in turn derives from System.Object .) The practical significance of this is that, besides the methods and properties and so on that you define, you also have access to a number of public and protected member methods that have been defined for the Object class. These methods are available in all other classes that you define.System.Object Methods
For the time being, we simply summarize the purpose of each method in the following list, and then, in the next section, we provide more detail about the ToString() method in particular. ❑ ToString() — This is intended as a fairly basic, quick - and - easy string representation; use it when you just want a quick idea of the contents of an object, perhaps for debugging purposes. It provides very little choice of how to format the data: For example, dates can in principle be expressed in a huge variety of different formats, but DateTime.ToString() does not offer you any choice in this regard. If you need a more sophisticated string representation that, for example, takes account of your formatting preferences or of the culture (the locale), then you should implement the IFormattable interface (see Chapter 8 , “ Strings and Regular Expressions ”). ❑ GetHashCode() — This is used if objects are placed in a data structure known as a map (also known as a hash table or dictionary). It is used by classes that manipulate these structures in order to determine where to place an object in the structure. If you intend your class to be used as a key for a dictionary, you will need to override GetHashCode() . Some fairly strict requirements exist for how you implement your overload, and you learn about those when you examine dictionaries in Chapter 10, “Collections.” ❑ Equals() (both versions) and ReferenceEquals() — As you ’ ll gather by the existence of three different methods aimed at comparing the equality of objects, the .NET Framework has quite a sophisticated scheme for measuring equality. Subtle differences exist between how these three methods, along with the comparison operator, == , are intended to be used. Not only that, but restrictions also exist on how you should override the virtual, one - parameter version of Equals() if you choose to do so, because certain base classes in the System.Collections namespace call the method and expect it to behave in certain ways. You explore the use of these methods in Chapter 6 , “ Operators and Casts, ” when you examine operators. ❑ Finalize() — This method is covered in Chapter 12 , “ Memory Management and Pointers. ” It is intended as the nearest that C# has to C++ - style destructors and is called when a reference object is garbage collected to clean up resources. The Object implementation of Finalize() actually does nothing and is ignored by the garbage collector. You will normally override Finalize() if an object owns references to unmanaged resources that need to be removed when the object is deleted. The garbage collector cannot do this directly because it only knows about managed resources, so it relies on any finalizers that you supply.❑ GetType() — This method returns an instance of a class derived from System.Type . This object can provide an extensive range of information about the class of which your object is a member, including base type, methods, properties, and so on. System.Type also provides the entry point into .NET ’ s reflection technology. Chapter 13 , “ Reflection, ” examines this topic. ❑ MemberwiseClone() — This is the only member of System.Object that isn ’ t examined in detail anywhere in the book. There is no need to because it is fairly simple in concept. It simply makes a copy of the object and returns a reference (or in the case of a value type, a boxed reference) to the copy. Note that the copy made is a shallow copy — this means that it copies all the value types in the class. If the class contains any embedded references, then only the references will be copied, not the objects referred to. This method is protected and so cannot be called to copy external objects. It is also not virtual, so you cannot override its implementation.
The ToString() Method
You ’ ve already encountered ToString() in Chapter 2 , “ C# Basics. ” It provides the most convenient way to get a quick string representation of an object. For example:int i = -50;
string str = i.ToString(); // returns “-50”Here ’ s another example:
enum Colors {Red, Orange, Yellow};
// later on in code...
Colors favoriteColor = Colors.Orange;
string str = favoriteColor.ToString(); // returns “Orange”Object.ToString() is actually declared as virtual, and all these examples are taking advantage of the fact that its implementation in the C# predefined data types has been overridden for us in order to return correct string representations of those types. You might not think that the Colors enum counts as a predefined data type. It actually gets implemented as a struct derived from System.Enum , and System.Enum has a rather clever override of ToString() that deals with all the enums you define. If you don ’ t override ToString() in classes that you define, your classes will simply inherit the System.Object implementation — which displays the name of the class. If you want ToString() to return a string that contains information about the value of objects of your class, you will need to override it. To illustrate this, the following example, Money , defines a very simple class, also called Money , which represents U.S. currency amounts. Money simply acts as a wrapper for the decimal class but supplies a ToString() method. Note that this method must be declared as override because it is replacing (overriding) the ToString() method supplied by Object . Chapter 4 discusses overriding in more detail. The complete code for this example is as follows. Note that it also illustrates use of properties to wrap fields:
using System;
namespace Sam.CSharp.OOCSharp
{
class MainEntryPoint
{
static void Main(string[] args)
{
Money cash1 = new Money();
cash1.Amount = 40M;Console.WriteLine(“cash1.ToString() returns: “ + cash1.ToString());
Console.ReadLine();
}
}
class Money
{
private decimal amount;
public decimal Amount
{
get
{
return amount;
}
set
{
amount = value;
}
}
public override string ToString()
{
return “$” + Amount.ToString();
}
}
}This example is here just to illustrate syntactical features of C#. C# already has a predefined type to represent currency amounts, decimal , so in real life, you wouldn ’ t write a class to duplicate this functionality unless you wanted to add various other methods to it. And in many cases, due to formatting requirements, you ’ d probably use the String.Format() method (which is covered in Chapter 8 ) rather than ToString() to display a currency string. In the Main() method, you first instantiate a Money object. The ToString() method is then called, which actually executes the override version of the method. Running this code gives the following results:
StringRepresentations
cash1.ToString() returns: $40
Extension Methods
There are many ways to extend a class. If you have the source for the class, then inheritance, which is covered in Chapter 4 , is a great way to add functionality to your objects. What if the source code isn ’ t available? Extension methods can help by allowing you to change a class without requiring the source code for the class. Extension methods are static methods that can appear to be part of a class without actually being in the source code for the class. Let ’ s say that the Money class from the previous example needs to have a method AddToAmount(decimal amountToAdd) . However, for whatever reason the original source for the assembly cannot be changed directly. All that you have to do is create a static class and add the AddToAmount method as a static method. Here is what the code would look like:namespace Chapter3.Extensions
{
public static class MoneyExtension{
public static void AddToAmount(this Money money, decimal amountToAdd)
{
money.Amount += amountToAdd;
}
}
}Notice the parameters for the AddToAmount method. For an extension method, the first parameter is the type that is being extended preceded by the this keyword. This is what tells the compiler that this method is part of the Money type. In this example Money is the type that is being extended. In the extension method you have access to all the public methods and properties of the type being extended. In the main program the AddToAmount method appears just as another method. The first parameter doesn ’ t appear, and you do not have to do anything with it. To use the new method, you make the call just like any other method:
cash1.AddToAmount(10M);Even though the extension method is static, you use standard instance method syntax. Notice that we called AddToAmount using the cash1 instance variable and not using the type name. If the extension method has the same name as a method in the class, the extension method will never be called. Any instance methods already in the class take precedence.