Chapter 4. Classes and Objects

In Chapter 3, we discussed the myriad types built into the C# language, such as int, long, and char. The heart and soul of C#, however, is the ability to create new, complex, programmer-defined types that map cleanly to the objects that make up the problem you are trying to solve.

One very powerful example of the use of “programmer defined types” is the .NET Framework that facilitates common tasks, such as interacting with databases and web sites. The Framework provides entire suites of controls, such as those used to interact with users, and those used to display data, as well as other programmer-defined types that manipulate data but have no visible aspect.

It is this ability to use and create powerful new types that characterizes an object-oriented language. You specify a new type in C# by defining a class. (You can also define types with interfaces, as you will see in Chapter 8.) Instances of a class are called objects. Objects are created in memory when your program executes.

The difference between a class and an object is the same as the difference between the concept of a dog and the particular dog who is sitting at your feet as you read this. You can’t play fetch with the definition of a dog, only with an instance.

A Dog class describes what dogs are like: they have weight, height, eye color, hair color, disposition, and so forth. They also have actions they can take, such as eat, walk, (eat), bark, (eat some more), and sleep. A particular dog (such as Jesse’s dog, Milo) has a specific weight (68 pounds), height (22 inches), eye color (black), hair color (yellow), disposition (angelic), and so forth. He is capable of all the actions of any dog (though if you knew him, you might imagine that eating is the only method he implements).

The huge advantage of classes in object-oriented programming is that they encapsulate the characteristics and capabilities of an entity within a single, self-contained unit of code. When you want to scroll an item in a Listbox into view, you tell the Listbox control to scroll. How it does so is of no concern to anyone but the person writing the Listbox control; that the listbox can be scrolled is all any other programmer needs to know. Encapsulation (the idea that an object is self-contained), along with polymorphism and inheritance (both of which are explained in Chapter 5), are the three cardinal principles of object-oriented programming.

An old programming joke asks, “How many object-oriented programmers does it take to change a light bulb?” Answer: none, you just tell the light bulb to change itself.

This chapter explains the C# language features that are used to create new types by creating classes. It will demonstrate how methods are used to define the behaviors of the class, and how the state of the class is accessed through properties, which act like methods to the developer of the class, but look like fields to clients of the class. The elements of the class—its behaviors and properties—are known collectively as its class members.

Defining Classes

To create a new class, you first declare it and then define its methods and fields. You declare a class using the class keyword. The complete syntax is as follows:

[attributes] [access-modifiers] class identifier [:[base-class
[,interface(s)]]
{class-body}

Tip

This is a formal definition diagram. Don’t let it intimidate you. The items in square brackets are optional.

You read this as follows: “a class is defined by an optional set of attributes followed by an optional set of access modifiers followed by the (nonoptional) keyword class, which is then followed by the (nonoptional) identifier (the class name).

“The identifier is optionally followed by the name of the base class, or if there is no base class, by the name of the first interface (if any). If there is a base class or an interface, the first of these will be preceded by a colon. If there is a base class and an interface, they will be separated by a comma, as will any subsequent interfaces.

“After all of these will be an open brace, the body of the class, and a closing brace.”

Although this can be confusing, an example makes it all much simpler:

public class Dog : Mammal
{
   // class body here
}

In this little example, public is the access modifier, Dog is the identifier, and Mammal is the base class.

Attributes are covered in Chapter 8; access modifiers are discussed in the next section. The identifier is the name of the class that you provide. The optional base-class is discussed in Chapter 5. The member definitions that make up the class-body are enclosed by open and closed curly braces ({}).

Tip

C and C++ programmers take note: a C# class definition does not end with a semicolon, though if you add one, the program will still compile.

In C#, everything happens within a class. So far, however, we’ve not created any instances of that class.

Tip

When you make an instance of a class, you are said to instantiate the class. The result of instantiating a class is the creation of an instance of the class, known as an object.

What is the difference between a class and an instance of that class (an object)? To answer that question, start with the distinction between the type int and a variable of type int. Thus, although you would write:

int myInteger = 5;

you wouldn’t write:

int = 5;  // won't compile

You can’t assign a value to a type; instead, you assign the value to an object of that type (in this case, a variable of type int, named myInteger).

When you declare a new class, you define the properties of all objects of that class, as well as their behaviors. For example, if you are creating a windowing environment, you might want to create screen widgets (known as controls in Windows programming) to simplify user interaction with your application. One control of interest might be a listbox, which is very useful for presenting a list of choices to the user and enabling the user to select from the list.

Listboxes have a variety of characteristics, called properties—for example, height, width, location, and text color. Programmers have also come to expect certain behaviors of listboxes, called methods: they can be opened, closed, sorted, and so on.

Object-oriented programming allows you to create a new type, ListBox, which encapsulates these characteristics and capabilities. You encapsulate the characteristics of the type in its properties, and its behaviors in its methods. Such a class might have properties named Height, Width, Location, and TextColor, and member methods named Sort( ), Add( ), Remove( ), and so on.

You can’t assign data to the ListBox class. Instead, you must first create an object of that type, as in the following code snippet:

ListBox myListBox;  // instantiate a ListBox object

The syntax for creating an instance of type Listbox (that is, a Listbox object named myListBox), is very similar to creating an instance of type integer named myAge:

int myAge;

that is, you place the type (ListBox) followed by the object identifier (myListBox) followed by a semicolon.

Once you create an instance of ListBox, you can assign data to it through its properties, and you can call its methods:

myListBox.Height = 50;
myListBox.TextColor = "Black";
myListBox.Sort(  );

Now, consider a class to keep track of and display the time of day. The internal state of the class must be able to represent the current year, month, date, hour, minute, and second. You probably would also like the class to display the time in a variety of formats. You might implement such a class by defining a single method and six variables, as shown in Example 4-1.

Example 4-1. Simple Time class
using System;

namespace TimeClass
{
  public class Time
  {
    // private variables
    int Year;
    int Month;
    int Date;
    int Hour;
    int Minute;
    int Second;

    // public methods
    public void DisplayCurrentTime(  )
    {
      Console.WriteLine(
      "stub for DisplayCurrentTime" );
    }
  }

  public class Tester
  {
    static void Main(  )
    {
      Time t = new Time(  );
      t.DisplayCurrentTime(  );
    }
  }
}

Tip

You will receive warnings when you compile this class that the member variables of Time (Year, Month, etc.) are never used. Please ignore these warnings for now (though it is generally not a good idea to ignore warnings unless you are certain you understand what they are and why you can ignore them). In this case, we are stubbing out the Time class, and if this were a real class, we would make use of these members in other methods.

The only method declared within the Time class definition is DisplayCurrentTime( ). The body of the method is defined within the class definition itself. All C# methods are defined inline as shown in Example 4-1 with DisplayCurrentTime( ).

Warning

From an advanced design viewpoint, there are two problems with this class. First, there is already a very good DateTime class in the Framework, and it makes no sense to reinvent one, except for demonstration purposes. Second, and more important, this class has two responsibilities: determining the time and displaying it. For anything other than a simple demonstration, you should divide those two responsibilities among two classes, assigning one well-defined set of responsibilities to each class.

The DisplayCurrentTime( ) method is defined to return void; that is, it will not return a value to whatever method invokes it. For now, the body of this method has been stubbed out.

The Time class definition starts with the declaration of a number of member variables: Year, Month, Date, Hour, Minute, and Second.

After the closing brace, a second class, Tester, is defined. Tester contains our now familiar Main( ) method. In Main( ), an instance of Time is created and its address is assigned to object t. Because t is an instance of Time, Main( ) can make use of the DisplayCurrentTime( ) method available with objects of that type and call it to display the time:

t.DisplayCurrentTime(  );

Access Modifiers

An access modifier determines which members of your class can be seen and used by methods of other classes. As you saw in the previous section, you also place an access modifier on the class itself, limiting the visibility of the class to methods of other classes. Table 4-1 summarizes the C# access modifiers.

Table 4-1. Access modifiers

Access modifier

Restrictions

public

No restrictions. Members marked public are visible to any method of any class.

private

The members in class A that are marked private are accessible only to methods of class A.

protected

The members in class A that are marked protected are accessible to methods of class A and to methods of classes derived from class A.

internal

The members in class A that are marked internal are accessible to methods of any class in A’s assembly.

protected internal

The members in class A that are marked protected internal are accessible to methods of class A, to methods of classes derived from class A, and to any class in A’s assembly. This is effectively protected OR internal. (There is no concept of protected AND internal.)

It is generally desirable to designate the member variables of a class as private. This means that only member methods of that class can access their value. Because private is the default accessibility level, you don’t need to make it explicit, but we recommend that you do so. Thus, in Example 4-1, the declarations of member variables should have been written as follows:

// private variables
private int Year;
private int Month;
private int Date;
private int Hour;
private int Minute;
private int Second;

The Tester class and DisplayCurrentTime( ) method are both declared public so that any other class can make use of them.

Tip

It is good programming practice to explicitly set the accessibility of all methods and members of your class.

Method Arguments

Methods can take any number of parameters.[3] The parameter list follows the method name and is enclosed in parentheses, with each parameter preceded by its type. For example, the following declaration defines a method named MyMethod( ), which returns void (that is, which returns no value at all) and which takes two parameters: an integer and a button.

void MyMethod (int firstParam, Button secondParam)
{
  // ...
}

Within the body of the method, the parameters act as local variables, as though you had declared them in the body of the method and initialized them with the values passed in. Example 4-2 illustrates how you pass values into a method—in this case, values of type int and float.

Example 4-2. Passing values into SomeMethod( )
using System;

namespace PassingValues
{
  public class MyClass
  {
    public void SomeMethod( int firstParam, float secondParam )
    {
      Console.WriteLine(
        "Here are the parameters received: {0}, {1}",
        firstParam, secondParam );
    }
  }

  public class Tester
  {
    static void Main(  )
    {
      int howManyPeople = 5;
      float pi = 3.14f;
      MyClass mc = new MyClass(  );
    mc.SomeMethod( howManyPeople, pi );
   }
  }
}

The method SomeMethod( ) takes an int and a float and displays them using Console.WriteLine( ). The parameters, which are named firstParam and secondParam, are treated as local variables within SomeMethod( ).

Tip

VB 6 programmers take note: C# methods don’t allow you to declare optional arguments. Instead, you have to use method overloading to create methods that declare different combinations of arguments. For more information, see the section "Overloading Methods and Constructors" later in this chapter.

In the calling method (Main), two local variables (howManyPeople and pi) are created and initialized. These variables are passed as the parameters to SomeMethod( ). The compiler maps howManyPeople to firstParam and pi to secondParam, based on their positions in the parameter list.

Creating Objects

In Chapter 3, we drew a distinction between value types and reference types. The primitive C# types (int, char, etc.) are value types, and when they are created as standalone variables (not as part of other objects) they are created on the stack. Objects, however, are reference types and are created on the heap, using the keyword new, as in the following:

Time t = new Time(  );

t doesn’t actually contain the value for the Time object; it contains the address of that (unnamed) object that is created on the heap. t itself is just a reference to that object.

Tip

VB 6 programmers take note: although there is a performance penalty in using the VB 6 keywords Dim and New on the same line, in C#, this penalty doesn’t exist. Thus, in C#, there is no drawback to using the new keyword when declaring an object variable.

Constructors

In Example 4-1, notice that the statement that creates the Time object looks as though it is invoking a method:

Time t = new Time(  );

In fact, a method is invoked whenever you instantiate an object. This method is called a constructor, and you must either define one as part of your class definition or let the compiler provide one on your behalf. The job of a constructor is to create the object specified by a class and to put it into a valid state. Before the constructor runs, the object is undifferentiated memory; after the constructor completes, the memory holds a valid instance of the class type.

The Time class of Example 4-1 doesn’t define a constructor. Because a constructor is not declared, the compiler provides one for you. The default constructor creates the object but takes no other action.

Member variables are initialized to innocuous values (integers to 0, strings to null, etc.).[4] Table 4-2 lists the default values assigned to primitive types.

Table 4-2. Primitive types and their default values

Type

Default value

numeric (int, long, etc.)

0

bool

false

char

'’ (null)

enum

0

Reference types

null

Typically, you’ll want to define your own constructor and provide it with arguments so that the constructor can set the initial state for your object. In Example 4-1, assume that you want to pass in the current year, month, date, and so forth so that the object is created with meaningful data.

To define a constructor, you declare a method whose name is the same as the class in which it is declared. Constructors have no return type and are typically declared public. If there are arguments to pass, you define an argument list just as you would for any other method. Example 4-3 declares a constructor for the Time class that accepts a single argument, an object of type DateTime.

Example 4-3. Declaring a constructor
using System;

namespace DeclaringConstructor
{
  public class Time
  {

    // private member variables
    int Year;
    int Month;
    int Date;
    int Hour;
    int Minute;
    int Second;

    // public accessor methods
    public void DisplayCurrentTime(  )
    {
      System.Console.WriteLine( "{0}/{1}/{2} {3}:{4}:{5}",
        Month, Date, Year, Hour, Minute, Second );
    }// constructor
    public Time( System.DateTime dt )
   {

      Year = dt.Year;

      Month = dt.Month;
      Date = dt.Day;
      Hour = dt.Hour;
      Minute = dt.Minute;
      Second = dt.Second;
    }
  }

  public class Tester
  {
    static void Main(  )
    {
      System.DateTime currentTime = System.DateTime.Now;
      Time t = new Time( currentTime );
      t.DisplayCurrentTime(  );
    }
  }
}

Output:
11/16/2007 16:21:40

In this example, the constructor takes a DateTime object and initializes all the member variables based on values in that object. When the constructor finishes, the Time object exists and the values have been initialized. When DisplayCurrentTime( ) is called in Main( ), the values are displayed.

Try commenting out one of the assignments and running the program again. You’ll find that the member variable is initialized by the compiler to 0. Integer member variables are set to 0 if you don’t otherwise assign them. Remember, value types (e.g., integers) can’t be uninitialized; if you don’t tell the constructor what to do, it will set the value types to their default values.

In Example 4-3, the DateTime object is created in the Main( ) method of Tester. This object, supplied by the System library, offers a number of public values—Year, Month, Day, Hour, Minute, and Second—that correspond directly to the private member variables of the Time object. In addition, the DateTime object offers a static member property, Now, which is a reference to an instance of a DateTime object initialized with the current time.

Tip

Static members are explained a bit later in this chapter.

Examine the highlighted line in Main( ), where the DateTime object is created by calling the static property Now. Now creates a DateTime value which, in this case, gets copied to the currentTime variable on the stack.

The currentTime variable is passed as a parameter to the Time constructor. The Time constructor parameter, dt, is a copy of the DateTime object.

Initializers

It is possible to initialize the values of member variables in an initializer, instead of having to do so in the constructor. You create an initializer by assigning an initial value to a class member:

private int Second = 30; // initializer

Assume that the semantics of the Time object are such that no matter what time is set, the seconds are always initialized to 30. You might rewrite the Time class to use an initializer so that no matter which constructor is called, the value of Second is always initialized, either explicitly by the constructor, or implicitly by the initializer. See Example 4-4.

Tip

Example 4-4 uses an overloaded constructor, which means that there are two versions of the constructor that differ by the number and type of parameters. We explain overloading constructors in detail later in this chapter.

Example 4-4. Using an initializer
using System;

namespace Initializer
{
  public class Time
  {
    // private member variables
    private int Year;
    private int Month;
    private int Date;
    private int Hour;
    private int Minute;
    private int Second = 30; // initializer

    // public accessor methods
    public void DisplayCurrentTime(  )
    {
      System.DateTime now = System.DateTime.Now;
      System.Console.WriteLine(
        "
Debug	: {0}/{1}/{2} {3}:{4}:{5}",
        now.Month, now.Day, now.Year, now.Hour,
        now.Minute, now.Second );

      System.Console.WriteLine( "Time	: {0}/{1}/{2} {3}:{4}:{5}",
        Month, Date, Year, Hour, Minute, Second );
    }

    // constructorspublic Time( System.DateTime dt )
    {
      Year = dt.Year;
      Month = dt.Month;
      Date = dt.Day;
      Hour = dt.Hour;
      Minute = dt.Minute;
      Second = dt.Second; //explicit assignment
    }

    public Time( int Year, int Month, int Date, int Hour, int Minute )
    {
      this.Year = Year;
      this.Month = Month;
      this.Date = Date;
      this.Hour = Hour;
      this.Minute = Minute;
    }
  }

  public class Tester
  {
    static void Main(  )
    {
      System.DateTime currentTime = System.DateTime.Now;
      Time t = new Time( currentTime );
      t.DisplayCurrentTime(  );

      Time t2 = new Time( 2007, 11, 18, 11, 45 );
      t2.DisplayCurrentTime(  );
    }
  }
}

Output:
Debug : 11/27/2007 7:52:54
Time : 11/27/2007 7:52:54

Debug : 11/27/2007 7:52:54
Time : 11/18/2007 11:45:30

If you don’t provide a specific initializer, the constructor will initialize each integer member variable to zero (0). In the case shown, however, the Second member is initialized to 30:

private int Second = 30; // initializer

If a value is not passed in for Second, its value will be set to 30 when t2 is created:

Time t2 = new Time(2007,11,18,11,45);
t2.DisplayCurrentTime(  );

However, if a value is assigned to Second, as is done in the constructor (which takes a DateTime object, shown in bold), that value overrides the initialized value.

The first time we invoke DisplayCurrentTime( ), we call the constructor that takes a DateTime object, and the seconds are initialized to 54. The second time the method is invoked, we explicitly set the time to 11:45 (not setting the seconds), and the initializer takes over.

If the program didn’t have an initializer and did not otherwise assign a value to Second, the value would be initialized by the CLR to 0.

Object Initializer

An alternative to calling the constructor to initialize the private member values of the Time class is to use a new feature in C# 3.0 called the object initializer, which allows you to initialize objects without invoking a constructor explicitly. In fact, you need not even define a constructor that initializes the object properties. All you need to do is to specify the value of each public property in the object initializer as shown in Example 4-5.

Example 4-5. Object Initializer
using System;

namespace ObjectInitializer
{
    public class Time
    {
        // private member variables
        public int Year;
        public int Month;
        public int Date;
        public int Hour;
        public int Minute;
        public int Second = 30; // initializer

        public void DisplayCurrentTime(  )
        {
            System.Console.WriteLine("Time	: {0}/{1}/{2} {3}:{4}:{5}",
              Month, Date, Year, Hour, Minute, Second);
        }

    }

    public class Tester
    {
        static void Main(  )
        {
            Time t = new Time {
              Year = 2009, Month = 7, Date = 10, Hour = 11, Minute = 15 };
            t.DisplayCurrentTime(  );
        }
    }
}

Output:
Time    : 7/10/2009 11:15:30

The following object initialization:

Time t = new Time {
  Year = 2009, Month = 7, Date = 10, Hour = 11, Minute = 15 };

Is the equivalent of creating the object and then assigning values to its public members:

Time t = new Time(  );
t.Year = 200;
t.Month = 7;
t.Date = 10;
t.Hour = 11;
t.Minute = 15;

Warning

The member values Year, Month, Date, Hour, and Minute would normally be declared private, and made public through properties, as described later in this chapter.

The this Keyword

The keyword this refers to the current instance of an object. The this reference (sometimes referred to as a this pointer[5]) is a hidden reference passed to every nonstatic method of a class. Each method can refer to the other methods and variables of its own object by way of the this reference.

The this reference is used in a number of ways. The first way is to qualify instance members otherwise hidden by parameters, as in the following:

public void SomeMethod (int hour)
{
  this.hour = hour;
}

In this example, SomeMethod( ) takes a parameter (hour) with the same name as a member variable of the class. The this reference is used to resolve the name ambiguity. Whereas this.hour refers to the member variable, hour refers to the parameter.

The argument in favor of this style is that you pick the right variable name and then use it for the parameter and the member variable. The counter argument is that using the same name for the parameter and the member variable can be confusing.

The second use of the this reference is to pass the current object as a parameter to another method. For instance:

class myClass
{
  public void Foo(OtherClass otherObject)
  {
    otherObject.Bar(this);
  }
}

Let’s unpack this example. Here, we have a method named myClass.Foo. In the body of this method, you invoke the Bar method of the OtherClass instance, passing in a reference to the current instance of myClass. This allows the Bar method to fiddle with the public methods and members of the current instance of myClass.

The third use of this is with indexers, covered in Chapter 9.

The fourth use of the this reference is to call one overloaded constructor from another, for example:

class myClass
{
  public myClass(int i) { //... }
  public myClass(  ) : this(42) { //... }
}

In this example, the default constructor invokes the overloaded constructor that takes an integer, by using the this keyword.

The final way that the this keyword is used is to explicitly invoke methods and members of a class as a form of documentation:

public void MyMethod(int y)
{
  int x = 0;
  x = 7; // assign to a local variable
  y = 8; // assign to a parameter
  this.z = 5; // assign to a member variable
  this.Draw(  ); // invoke member method
}

In the cases shown, the use of the this reference is superfluous, but may make the programmer’s intent clearer and does no harm (except, arguably, to clutter the code).

Using Static Members

The members of a class (variables, methods, events, indexers, etc.) can be instance members or static members. Instance members are associated with instances of a type, whereas static members are considered to be part of the class. Thus, there is one instance member for each object instantiated, but there is always exactly one static member, no matter how many objects are created (even if no objects are created).

You access a static member through the name of the class in which it is declared. For example, suppose you have a class named Button and have instantiated two objects of that class named btnUpdate and btnDelete.[6] Suppose as well that the Button class has a static method SomeMethod( ). To access the static method, you write:

Button.SomeMethod(  );

rather than:

btnUpdate.SomeMethod(  );

In C#, it is not legal to access a static method or member variable through an instance, and trying to do so will generate a compiler error.

Tip

VB 6 programmers take note: don’t confuse the static keyword in C# with the Static keyword in VB 6 and VB.NET. In VB, the Static keyword declares a variable that is available only to the method in which it was declared. In other words, the Static variable is not shared among different objects of its class (i.e., each Static variable instance has its own value). However, this variable exists for the life of the program, which allows its value to persist from one method call to another.

In C#, the static keyword indicates a class member. In VB, the equivalent keyword is Shared.

Static methods act more or less like global methods, in that you can invoke them without actually having an instance of the object at hand. The advantage of static methods over global, however, is that the name is scoped to the class in which it occurs, and thus you don’t clutter up the global namespace with myriad function names. This can help manage highly complex programs, and the name of the class acts very much like a namespace for the static methods within it.

In addition, static methods may be passed instance members as parameters (or may create such instances themselves within the static method). Because they are scoped to the class—instead of being scoped globally—they have access to the private members of the instances.

Tip

Resist the temptation to create a single class in your program in which you stash all your miscellaneous methods. It is possible but not desirable, and it undermines the encapsulation of an object-oriented design.

Invoking Static Methods

The Main( ) method is static. Static methods are said to operate on the class, rather than on an instance of the class. They don’t have a this reference, as there is no instance to point to.

Tip

Java programmers take note: in C#, calling static methods through instance variables is not permitted.

Static methods can’t directly access nonstatic members. For Main( ) to call a nonstatic method, it must instantiate an object. Consider Example 4-2, which was shown earlier.

SomeMethod( ) is a nonstatic method of MyClass. For Main( ) to access this method, it must first instantiate an object of type MyClass and then invoke the method through that object.

Using Static Fields

A common way to demonstrate the use of static member variables is to keep track of the number of instances that currently exist for your class. Example 4-6 illustrates.

Example 4-6. Using static fields for instance counting
using System;

namespace StaticFields
{
  public class Cat
  {

    private static int instances = 0;

    public Cat(  )
    {
      instances++;
    }

    public static void HowManyCats(  )
    {
      Console.WriteLine( "{0} cats adopted", instances );
    }
  }

  public class Tester
  {
    static void Main(  )
    {
      Cat.HowManyCats(  );
      Cat frisky = new Cat(  );
      Cat.HowManyCats(  );
      Cat whiskers = new Cat(  );
      Cat.HowManyCats(  );
    }
  }
}

Output:
0 cats adopted
1 cats adopted
2 cats adopted

The Cat class has been stripped to its absolute essentials. A static member variable called instances is created and initialized to 0. Note that the static member is considered part of the class, not a member of an instance, and so it can’t be initialized by the compiler on creation of an instance. Thus, if you want to initialize a static member, you must provide an explicit initializer. When additional instances of Cats are created (in a constructor), the count is incremented.

Destroying Objects

Because C# provides garbage collection, you never need to explicitly destroy your objects. However, if your object controls unmanaged resources, you will need to explicitly free those resources when you are done with them. Implicit control over unmanaged resources is provided by a finalizer, which will be called by the garbage collector when your object is destroyed.

The finalizer should only release resources that your object holds on to, and should not reference other objects. Note that if you have only managed references, you don’t need to and should not implement a finalizer; you want this only for handling unmanaged resources. Because there is some cost to having a finalizer, you ought to implement this only on methods that require it (i.e., methods that consume valuable unmanaged resources).

You can’t call an object’s finalizer directly. The garbage collector will call it for you.

Finalizers Versus Dispose

It is not legal to call a finalizer explicitly. Your finalizer will be called by the garbage collector. If you do handle precious unmanaged resources (such as file handles) that you want to close and dispose of as quickly as possible, you ought to implement the IDisposable interface.[7] (You will learn more about interfaces in Chapter 8.) The IDisposable interface requires its implementers to define one method, named Dispose( ), to perform whatever cleanup you consider to be crucial. The availability of Dispose( ) is a way for your clients to say, “Don’t wait for the finalizer to be called, do it right now.”

If you provide a Dispose( ) method, you should stop the garbage collector from calling your object’s finalizer. To do so, call the static method GC.SuppressFinalize( ), passing in the this pointer for your object. Your finalizer can then call your Dispose( ) method. Thus, you might write:

using System;
class Testing : IDisposable
{
  bool is_disposed = false;
  protected virtual void Dispose(bool disposing)
  {
    if (!is_disposed) // only dispose once!
    {
      if (disposing)
      {
        Console.WriteLine(
        "Not in finalizer, OK to reference other objects");
      }
      // perform cleanup for this object
      Console.WriteLine("Disposing...");
    }
    this.is_disposed = true;
  }

  public void Dispose(  )
  {
    Dispose(true);
    // tell the GC not to finalize
    GC.SuppressFinalize(this);
  }

  ~Testing(  )
  {
    Dispose(false);
    Console.WriteLine("In finalizer.");
  }
}

Implementing the Close( ) Method

For some objects, you may prefer to have your clients call a method named Close( ). (For example, Close( ) may make more sense than Dispose( ) for file objects.) You can implement this by creating a private Dispose( ) method and a public Close( ) method, and having your Close( ) method invoke Dispose( ).

The using Statement

To make it easier for your clients to properly dispose of your objects, C# provides a using statement that ensures that Dispose( ) will be called at the earliest possible time. The idiom is to declare the objects you are using and then to create a scope for these objects with curly braces. When the closing brace is reached, the Dispose( ) method will be called on the object automatically, as illustrated in Example 4-7.

Example 4-7. The using statement
using System.Drawing;

namespace usingStatement
{
  class Tester
  {
    public static void Main(  )
    {
      using ( Font theFont = new Font( "Arial", 10.0f ) )
      {
        // use theFont
      } // compiler will call Dispose on theFont

      Font anotherFont = new Font( "Courier", 12.0f );

      using ( anotherFont )
      {
        // use anotherFont
      } // compiler calls Dispose on anotherFont
    }
  }
}

In the first part of this example, the Font object is created within the using statement. When the using statement ends, Dispose( ) is called on the Font object.

In the second part of the example, a Font object is created outside the using statement. When we decide to use that font, we put it inside the using statement; when that statement ends, Dispose( ) is called once again.

This second approach is fraught with danger. If an exception is thrown after the object is created, but before the using block is begun, the object will not be disposed. Second, the variable remains in scope after the using block ends, but if it is accessed, it will fail.

The using statement also protects you against unanticipated exceptions. Regardless of how control leaves the using statement (including you placing a return statement in the middle of the block), Dispose( ) is called. An implicit try-finally block is created for you. (See Chapter 11 for details.)

Passing Parameters

By default, value types are passed into methods by value. (See the section "Method Arguments" earlier in this chapter.) This means that when a value object is passed to a method, a temporary copy of the object is created within that method. Once the method completes, the copy is discarded. Although passing by value is the normal case, there are times when you will want to pass value objects by reference. C# provides the ref parameter modifier for passing value objects into a method by reference, and the out modifier for those cases in which you want to pass in a variable by reference without first initializing it.

C# also supports the params modifier, which allows a method to accept a variable number of parameters. We discuss the params keyword in Chapter 9.

Passing by Reference

Methods can return only a single value (though that value can itself be a collection of values). Let’s return to the Time class and add a GetTime( ) method, which returns the hour, minutes, and seconds.

Because you can’t return three values, perhaps you can pass in three parameters, let the method modify the parameters, and examine the result in the calling method. Example 4-8 shows a first attempt at this.

Example 4-8. Returning values in parameters
using System;

namespace ReturningValuesInParams
{
  public class Time
  {
    // private member variables
    private int Year;
    private int Month;
    private int Date;
    private int Hour;
    private int Minute;
    private int Second;

    // public accessor methods
    public void DisplayCurrentTime(  )
    {
       System.Console.WriteLine( "{0}/{1}/{2} {3}:{4}:{5}",
         Month, Date, Year, Hour, Minute, Second );
    }

    public int GetHour(  )
    {
      return Hour;
    }

    public void GetTime( int h, int m, int s )
    {
      h = Hour;
      m = Minute;
      s = Second;
    }

    // constructor
    public Time( System.DateTime dt )
    {

      Year = dt.Year;
      Month = dt.Month;
      Date = dt.Day;
      Hour = dt.Hour;
      Minute = dt.Minute;
      Second = dt.Second;
    }
  }

  public class Tester
  {
    static void Main(  )
    {
      System.DateTime currentTime = System.DateTime.Now;
      Time t = new Time( currentTime );
      t.DisplayCurrentTime(  );

      int theHour = 0;
      int theMinute = 0;
      int theSecond = 0;
      t.GetTime( theHour, theMinute, theSecond );
      System.Console.WriteLine( "Current time: {0}:{1}:{2}",
        theHour, theMinute, theSecond );
    }
  }
}

Output:
11/17/2007 13:41:18
Current time: 0:0:0

Notice that the Current time in the output is 0:0:0. Clearly, this first attempt did not work. The problem is with the parameters. You pass in three integer parameters to GetTime( ), where they are modified, but when the values are accessed back in Main( ), they are unchanged. This is because integers are value types, and so are passed by value; a copy is made as they are passed to GetTime( ), and the original values in Main( ) are left unchanged. What you need is to pass these values by reference.

Two small changes are required. First, change the parameters of the GetTime( ) method to indicate that the parameters are ref (reference) parameters:

public void GetTime(ref int h, ref int m, ref int s)
{
   h = Hour;
   m = Minute;
   s = Second;
}

Second, modify the call to GetTime( ) to pass the arguments as references:

t.GetTime(ref theHour, ref theMinute, ref theSecond);

Warning

If you leave out the second step of marking the arguments with the keyword ref, the compiler will complain that the argument can’t be converted from an int to a ref int.

The results will now show the correct time. By declaring these parameters to be ref parameters, you instruct the compiler to pass them by reference, and instead of a copy being made, the parameter in GetTime( ) will be a reference to the same variable (theHour) that was created in Main( ). When you change these values in GetTime( ), the change will be reflected in Main( ) because the references will be referring to the same values in memory.

Keep in mind that ref parameters are references to the actual original value: it is as though you said, “Here, work on this one.” Conversely, value parameters are copies: it is as though you said, “Here, work on one just like this.”

Overcoming Definite Assignment with “out” Parameters

C# imposes definite assignment, which requires that all variables be assigned a value before they are used. In Example 4-8, if you don’t initialize theHour, theMinute, and theSecond before you pass them as parameters to GetTime( ), the compiler will complain. Yet, the initialization that is done merely sets their values to 0 before they are passed to the method:

int theHour = 0;
int theMinute = 0;
int theSecond = 0;
t.GetTime( ref theHour, ref theMinute, ref theSecond);

There is no logical reason to set these values to zero; our intent is to pass them to GetTime and have them set to a meaningful value there. The problem is that definite assignment won’t let unassigned variables be passed as parameters. If you try, you will receive the following compiler errors:

Use of unassigned local variable 'theHour'
Use of unassigned local variable 'theMinute'
Use of unassigned local variable 'theSecond'

One solution, as shown here, is to initialize the variables to a meaningless value−in this case, zero. But assigning a value should have semantic meaning within your program; it shouldn’t be done just to satisfy a technical requirement that doesn’t apply in this circumstance. That is why C# provides the out parameter modifier.

The out modifier suspends the requirement that a reference parameter be initialized. The parameters to GetTime( ), for example, provide no information to the method; they are simply a mechanism for getting information from the method. Thus, by marking all three as out parameters, you eliminate the need to initialize them outside the method.

The out parameter thus increases the self-documentation (and long-term reliability) of the program in two ways:

  • It removes a misleading assignment that appeared to assign a meaningful value but actually was included just to silence the compiler.

  • It documents the fact that the three parameters are passed to the GetTime method only to extract information and provide no meaningful information on the way in.

Within the called method, the out parameters must be assigned a value before the method returns (failure to do so will cause a compile error). The following are the altered parameter declarations for GetTime( ):

public void GetTime(out int h, out int m, out int s)
{
  h = Hour;
  m = Minute;
  s = Second;
}

And, here is the new invocation of the method in Main( ):

t.GetTime(out theHour, out theMinute, out theSecond);

To summarize, value types are passed into methods by value. ref parameters are used to pass value types into a method by reference. This allows you to retrieve their modified values in the calling method. out parameters are used only to return information from a method. Example 4-9 rewrites Example 4-8 to use all three.

Example 4-9. Using in, out, and ref parameters
using System;

namespace InOutRef
{
  public class Time
  {
    // private member variables
    private int Year;
    private int Month;
    private int Date;
    private int Hour;
    private int Minute;
    private int Second;

    // public accessor methods
    public void DisplayCurrentTime(  )
    {
      System.Console.WriteLine( "{0}/{1}/{2} {3}:{4}:{5}",
        Month, Date, Year, Hour, Minute, Second );
    }

    public int GetHour(  )
    {
      return Hour;
    }public void SetTime( int hr, out int min, ref int sec )
    {
      // if the passed in time is >= 30
      // increment the minute and set second to 0
      // otherwise leave both alone
      if ( sec >= 30 )
      {
        Minute++;
        Second = 0;
      }
      Hour = hr; // set to value passed in

      // pass the minute and second back out
      min = Minute;
      sec = Second;
    }

    // constructor
    public Time( System.DateTime dt )
    {
      Year = dt.Year;
      Month = dt.Month;
      Date = dt.Day;
      Hour = dt.Hour;
      Minute = dt.Minute;
      Second = dt.Second;
    }
  }

  public class Tester
  {
    static void Main(  )
    {
      System.DateTime currentTime = System.DateTime.Now;
      Time t = new Time( currentTime );
      t.DisplayCurrentTime(  );

      int theHour = 3;
      int theMinute;
      int theSecond = 20;

      t.SetTime( theHour, out theMinute, ref theSecond );
      System.Console.WriteLine(
        "the Minute is now: {0} and {1} seconds",
        theMinute, theSecond );

      theSecond = 40;
      t.SetTime( theHour, out theMinute, ref theSecond );
      System.Console.WriteLine( "the Minute is now: " +
        "{0} and {1} seconds", theMinute, theSecond );
    }
  }
}

Output:
11/17/2007 14:6:24
the Minute is now: 6 and 24 seconds
the Minute is now: 7 and 0 seconds

SetTime is a bit contrived, but it illustrates the three types of parameters. theHour is passed in as a value parameter; its entire job is to set the member variable Hour, and no value is returned using this parameter.

The ref parameter theSecond is used to set a value in the method. If theSecond is greater than or equal to 30, the member variable Second is reset to 0 and the member variable Minute is incremented.

Finally, theMinute is passed into the method only to return the value of the member variable Minute, and thus is marked as an out parameter.

It makes perfect sense that theHour and theSecond must be initialized—their values are needed and used. It is not necessary to initialize theMinute, as it is an out parameter that exists only to return a value. What at first appeared to be arbitrary and capricious rules now make sense; values are required to be initialized only when their initial value is meaningful.

Overloading Methods and Constructors

Often, you’ll want to have more than one function with the same name. The most common example of this is to have more than one constructor. In the examples shown so far, the constructor has taken a single parameter: a DateTime object. It would be convenient to be able to set new Time objects to an arbitrary time by passing in year, month, date, hour, minute, and second values. It would be even more convenient if some clients could use one constructor, and other clients could use the other constructor. Function overloading provides for these exact contingencies.

The signature of a method is defined by its name and its parameter list.. Parameter lists can differ by having different numbers or different types of parameters. For example, in the following code, the first method differs from the second in the number of parameters, and the second differs from the third in the types of parameters:

void myMethod(int p1);
void myMethod(int p1, int p2);
void myMethod(int p1, string s1);

A class can have any number of methods, as long as each one’s signature differs from that of all the others.

Overloaded methods may have different return types, but changing only the return type does not change the signature, and thus is not sufficient to overload the method.

void myMethod(int p1);
string myMethod(int p1, int p2);  // legal

void otherMethod(int p1);
string otherMethod(int p1);  // not legal

In the snippet shown here, myMethod is overloaded by changing the number of parameters, and the two versions may have different return types. On the other hand, the signature of otherMethod is unchanged in the two versions, and changing the return type is not enough to overload the method.

Example 4-10 illustrates the Time class with two constructors: one that takes a DateTime object, and the other that takes six integers.

Example 4-10. Overloading the constructor
using System;

namespace OverloadedConstructor
{
  public class Time
  {
    // private member variables
    private int Year;
    private int Month;
    private int Date;
    private int Hour;
    private int Minute;
    private int Second;

    // public accessor methods
    public void DisplayCurrentTime(  )
    {
      Console.WriteLine( "{0}/{1}/{2} {3}:{4}:{5}",
        Month, Date, Year, Hour, Minute, Second );
    }

    // constructors
    public Time( System.DateTime dt )
    {
      Year = dt.Year;
      Month = dt.Month;
      Date = dt.Day;
      Hour = dt.Hour;
      Minute = dt.Minute;
      Second = dt.Second;
    }

    public Time( int Year, int Month, int Date,
      int Hour, int Minute, int Second )
    {
      this.Year = Year;
      this.Month = Month;
      this.Date = Date;
      this.Hour = Hour;
      this.Minute = Minute;
      this.Second = Second;
    }
  }

  public class Tester
  {
    static void Main(  )
    {
      DateTime currentTime = DateTime.Now;

      Time t1= new Time( currentTime );
      t.DisplayCurrentTime(  );

      Time t2 = new Time( 2007, 11, 18, 11, 03, 30 );
      t2.DisplayCurrentTime(  );

    }
  }
}
Output:

11/20/2007 17:7:54
11/18/2007 11:3:30

As you can see, the Time class in Example 4-10 has two constructors. If a function’s signature consisted only of the function name, the compiler would not know which constructors to call when constructing t1 and t2. However, because the signature includes the function argument types, the compiler is able to match the constructor call for t1 with the constructor whose signature requires a DateTime object. Likewise, the compiler is able to associate the t2 constructor call with the constructor method whose signature specifies six integer arguments.

When you overload a method, you must change the signature (i.e., the name, number, or type of the parameters). You are free, as well, to change the return type, but this is optional. Changing only the return type doesn’t overload the method, and creating two methods with the same signature but differing return types will generate a compile error, as you can see in Example 4-11.

Example 4-11. Varying the return type on overloaded methods
using System;

namespace VaryingReturnType
{
  public class Tester
  {
    private int Triple( int val )
    {
      return 3 * val;
    }

    private long Triple( long val )
    {
      return 3 * val;
    }

    public void Test(  )
    {
      int x = 5;
      int y = Triple( x );
      System.Console.WriteLine( "x: {0} y: {1}", x, y );

      long lx = 10;
      long ly = Triple( lx );
      System.Console.WriteLine( "lx: {0} ly: {1}", lx, ly );

    }
    static void Main(  )
    {
      Tester t = new Tester(  );
      t.Test(  );
    }
  }
}
Output:

x: 5 y: 15
lx: 10 ly: 30

In this example, the Tester class overloads the Triple( ) method, one to take an integer, the other to take a long. The return type for the two Triple( ) methods varies. Although this is not required, it is very convenient in this case.

Encapsulating Data with Properties

Properties allow clients to access the state of the object as though they were accessing member fields directly, while actually implementing that access through a class method.

This is ideal. The client wants direct access to the state of the object and doesn’t want to work with methods. The class designer, however, wants to hide the internal state of his class in class members, and provide indirect access through a method.

By decoupling the class state from the method that accesses that state, the designer is free to change the internal state of the object as needed. When the Time class is first created, the Hour value might be stored as a member variable. When the class is redesigned, the Hour value might be computed or retrieved from a database. If the client had direct access to the original Hour member variable, the change to computing the value would break the client. By decoupling and forcing the client to go through a method (or property), the Time class can change how it manages its internal state without breaking client code.

Properties meet both goals: they provide a simple interface to the client, appearing to be a member variable. They are implemented as methods, however, providing the data-hiding required by good object-oriented design, as illustrated in Example 4-12.

Example 4-12. Using a property
using System;

namespace UsingAProperty
{
  public class Time
  {
    // private member variables
    private int year;
    private int month;
    private int date;
    private int hour;
    private int minute;
    private int second;

    // public accessor methods
    public void DisplayCurrentTime(  )
    {

      Console.WriteLine(
        "Time	: {0}/{1}/{2} {3}:{4}:{5}",
        month, date, year, hour, minute, second );
    }

    // constructors
    public Time( System.DateTime dt )
    {
      year = dt.Year;
      month = dt.Month;
      date = dt.Day;
      hour = dt.Hour;
      minute = dt.Minute;
      second = dt.Second;
    }

    // create a property

    public int Hour
    {
      get
      {
        return hour;
      }

      set
      {
        hour = value;
      }
    }
  }

  public class Tester
  {
    static void Main(  )
    {
      DateTime currentTime = DateTime.Now;
      Time t = new Time( currentTime );
      t.DisplayCurrentTime(  );

      int theHour = t.Hour;
      Console.WriteLine( "
Retrieved the hour: {0}
", theHour );
      theHour++;
      t.Hour = theHour;
      Console.WriteLine( "Updated the hour: {0}
", theHour );
    }
  }
}

Output:

Time    : 9/20/2007 17:16:42

Retrieved the hour: 17

Updated the hour: 18

To declare a property, write the property type and name followed by a pair of braces. Within the braces, you may declare get and set accessors. Neither of these has explicit parameters, though the set( ) accessor has an implicit parameter value, as shown next.

In Example 4-12, Hour is a property. Its declaration creates two accessors: get and set.

public int Hour
{
  get
  {
    return hour;
  }

  set
  {
    hour = value;
  }
}

Each accessor has an accessor body that does the work of retrieving or setting the property value. The property value might be stored in a database (in which case, the accessor body would do whatever work is needed to interact with the database), or it might just be stored in a private member variable:

private int hour;

The get Accessor

The body of the get accessor is similar to a class method that returns an object of the type of the property. In the example, the accessor for Hour is similar to a method that returns an int. It returns the value of the private member variable in which the value of the property has been stored:

get
{
  return hour;
}

In this example, a local int member variable is returned, but you could just as easily retrieve an integer value from a database, or compute it on the fly.

Whenever you read the property, the get accessor is invoked:

Time t = new Time(currentTime);
int theHour = t.Hour;

In this example, the value of the Time object’s Hour property is retrieved, invoking the get accessor to extract the property, which is then assigned to a local variable.

The set Accessor

The set accessor sets the value of a property and is similar to a method that returns void. When you define a set accessor, you must use the value keyword to represent the argument whose value is passed to and stored by the property:

set
{
  hour = value;
}

Here again, a private member variable is used to store the value of the property, but the set accessor could write to a database or update other member variables as needed.

When you assign a value to the property, the set accessor is automatically invoked, and the implicit parameter value is set to the value you assign:

theHour++;
t.Hour = theHour;

The two main advantages of this approach are that the client can interact with the properties directly, without sacrificing the data-hiding and encapsulation sacrosanct in good object-oriented design, and that the author of the property can ensure that the data provided is valid.

Property Access Modifiers

It is possible to set an access modifier (protected, internal, private) to modify access to either the get or set accessor. To do so, your property must have both a set and a get accessor, and you may modify only one or the other. Also, the modifier must be more restrictive than the accessibility level already on the property or the indexer (thus, you may add protected to the get or set accessor of a public property, but not to a private property):

public string MyString
{
  protected get { return myString; }
  set { myString = value; }
}

In this example, access to the get accessor is restricted to methods of this class and classes derived from this class, whereas the set accessor is publicly visible.

Tip

Note that you may not put an access modifier on an interface (see Chapter 8) or on explicit interface member implementation. In addition, if you are overriding a virtual property or index (as discussed next), the access modifier must match the base property’s access modifier.

Automatic Properties

It is very common to write something like the following (often repeatedly) in your classes:

private int hour;

public int Hour
{
    get { return hour; }
    set { hour = value; }
}

Recognizing this, the C# 3.0 compiler will allow you to write the following, as a shortcut for the previous code:

public int Hour { get; set; }

The net effect is exactly the same: the compiler generates the same underlying code for you, and you are free at any time to fill in the accessors and the backing variable yourself, but this shortcut saves many hours of coding and is wonderfully convenient.

Note that you can only use automatic properties when you are willing to have a get and a set accessor, and then when simple implementation (as shown) is sufficient.

There is a workaround if you want only a get or set accessor: You can mark either one as private. Thus, you can create a read-only property by writing:

public int Hour { get; private set; }

And, you can create a write-only property by writing:

public int Hour { private get; set; }

If you need to use attributes, or if you in any other way modify the simplicity of the backing variable or the accessors, you must use the normal property syntax.

readonly Fields

You might want to create a version of the Time class that is responsible for providing public static values representing the current time and date. Example 4-13 illustrates a simple approach to this problem.

Example 4-13. Using static public constants
using System;

namespace StaticPublicConstants
{
  public class RightNow
  {
    // public member variables
    public static int Year;
    public static int Month;
    public static int Date;
    public static int Hour;
    public static int Minute;
    public static int Second;

    static RightNow(  )
    {
      DateTime dt = DateTime.Now;
      Year = dt.Year;
      Month = dt.Month;
      Date = dt.Day;
      Hour = dt.Hour;
      Minute = dt.Minute;
      Second = dt.Second;
    }
  }

  public class Tester
  {
    static void Main(  )
    {
      Console.WriteLine( "This year: {0}",
        RightNow.Year.ToString(  ) );
      RightNow.Year = 2008;
      Console.WriteLine( "This year: {0}",
        RightNow.Year.ToString(  ) );
    }
  }
}

Output:
This year: 2007
This year: 2008

This works well enough, until someone comes along and changes one of these values. As the example shows, the RightNow.Year value can be changed, for example, to 2008. This is clearly not what we’d like.

You’d like to mark the static values as constant, but that is not possible because you don’t initialize them until the static constructor is executed. C# provides the keyword readonly for exactly this purpose. If you change the class member variable declarations as follows:

public static readonly int Year;
public static readonly int Month;
public static readonly int Date;
public static readonly int Hour;
public static readonly int Minute;
public static readonly int Second;

and then comment out the reassignment in Main( ):

// RightNow.Year = 2008; // error!

the program will compile and run as intended.



[3] * The terms argument and parameter are often used interchangeably, though some programmers insist on differentiating between the parameter declaration and the arguments passed in when the method is invoked.

[4] * When you write your own constructor, you’ll find that these values have been initialized before the constructor runs. In a sense, there are two steps to building new objects—some CLR-level magic that zeros out all the fields and does whatever else needs to be done to make the thing a valid object, and then the steps in the constructor you create (if any).

[5] * A pointer is a variable that holds the address of an object in memory. C# doesn’t use pointers with managed objects. Some C++ programmers have become so used to talking about a this pointer that they’ve carried the term over (incorrectly) to C#. We’ll refer to the this reference, and pay a 25-cent fine to charity each time we forget.

[6] * As noted earlier, btnUpdate and btnDelete are actually variables that refer to the unnamed instances on the heap. For simplicity, we’ll refer to these as the names of the objects, keeping in mind that this is just shorthand for “the name of the variables that refer to the unnamed instances on the heap.”

[7] * Most of the time, you will not write classes that deal with unmanaged resources such as raw handles directly. You may, however, use wrapper classes such as FileStream and Socket, but these classes do implement IDisposable, in which case, you ought to have your class implement IDisposable (but not a finalizer). Your Dispose method will call Dispose on any disposable resources that you’re using.

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

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