Chapter 4. Classes and Objects

Chapter 3 discusses the myriad primitive 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.

It is this ability to create new types that characterizes an object-oriented language. Specify new types in C# by declaring and defining classes. 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, bark, and sleep. A particular dog (such as my dog Milo) has a specific weight (62 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 in a single, self-contained and self-sustaining unit of code. When you want to sort the contents of an instance of a Windows list box control, for example, tell the list box to sort itself. How it does so is of no concern; that it does so is all you need to know. Encapsulation, along with polymorphism and inheritance, is one of 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. (Alternate answer: none, Microsoft has changed the standard to darkness.)

This chapter explains the C# language features that are used to specify new classes. The elements of a class -- its behaviors and properties -- are known collectively as its class members. This chapter will show how methods are used to define the behaviors of the class, and how the state of the class is maintained in member variables (often called fields ). In addition, this chapter introduces properties , which act like methods to the creator of the class but look like fields to clients of the class.

Defining Classes

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

[attributes] [access-modifiers] class identifier [:base-class]
{class-body}

Attributes are covered in Chapter 8; access modifiers are discussed in the next section. (Typically, your classes will use the keyword public as an access modifier.) 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++ 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. For instance, some of the examples in Chapter 3 make use of a class named Tester:

               public class Tester
{

        public static int Main( )
        {
         /...
        }
}

So far, we’ve not instantiated any instances of that class; that is, we haven’t created any Tester objects. What is the difference between a class and an instance of that class? To answer that question, start with the distinction between the type int and a variable of type int. Thus, while you would write:

int myInteger = 5;

you would not write:

int = 5;

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

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, more commonly known as controls in Windows programming, to simplify user interaction with your application. One control of interest might be a list box, which is very useful for presenting a list of choices to the user and enabling the user to select from the list.

List boxes have a variety of characteristics -- for example, height, width, location, and text color. Programmers have also come to expect certain behaviors of list boxes: 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. Such a class might have member variables named height, width, location, and text_color, and member methods named sort( ), add( ), remove( ), etc.

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

ListBox myListBox;

Once you create an instance of ListBox, you can assign data to its fields.

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;

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

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


   }

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

   }

The only method declared within the Time class definition is the method DisplayCurrentTime( ). The body of the method is defined within the class definition itself. Unlike other languages (such as C++), C# does not require that methods be declared before they are defined, nor does the language support placing its declarations into one file and code into another. (C# has no header files.) All C# methods are defined inline as shown in Example 4-1 with DisplayCurrentTime( ).

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

The Time class definition ends 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 class methods -- including methods of other classes -- can see and use a member variable or method within a class. 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 also 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 also 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 do not need to make it explicit, but I 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;

Class Tester and method DisplayCurrentTime( ) 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. Although you can rely on the fact that class members are declared private by default, making their access explicit indicates a conscious decision and is self-documenting.

Method Arguments

Methods can take any number of parameters.[6] The parameter list follows the method name and is encased 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 int and a button:

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

Within the body of the method, the parameters act as local variables, as if 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;

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

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 relative positions in the parameter list.



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

Get Programming C#, Second Edition now with the O’Reilly learning platform.

O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.