Chapter 3 discusses the intrinsic types, built into the C# language. As you may recall, these simple types allow you to hold and manipulate numeric values and strings. The true power of C#, however, lies in its capacity to let the programmer define new types to suit particular problems. It is this ability to create new types that characterizes an object-oriented language. You specify new types in C# by declaring and defining classes.
Particular instances of a class are called objects. 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) will have 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—methods, in programming parlance—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 classes encapsulate the characteristics and capabilities of a type in a single, self-contained unit.
Suppose, for instance, you want to sort the contents of an instance of a Windows listbox control. The listbox control is defined as a class. One of the properties of that class is that it knows how to sort itself. Sorting is encapsulated within the class, and the details of how the listbox sorts itself are not made visible to other classes. If you want a listbox sorted, you just tell the listbox to sort itself, and it takes care of the details.
So, you simply write a method that tells the listbox to sort itself—and that’s what happens. How it sorts is of no concern; that it does so is all you need to know.
As noted in Chapter 6, this is called encapsulation, which, along with polymorphism and specialization, is one of three cardinal principles of object-oriented programming. Chapter 11 discusses polymorphism and inheritance.
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 specify new classes . The elements of a class—its behaviors and its state—are known collectively as its class members.
Class behavior is created by writing methods (sometimes called
member functions). A method is a routine that every object of the class
can execute. For example, a Dog
class
might have a Bark
method, and a
listbox
class might have a Sort
method.
Class state is maintained by fields (sometimes
called member variables). Fields may be primitive types (an int
to hold the age of the dog or a set of
strings to hold the contents of the listbox), or fields may be objects of
other classes (for example, an Employee
class may have a field of type Address
).
Finally, classes may also have properties, which act like methods to the creator of the class, but look like fields to clients of the class. A client is any object that interacts with instances of the class.
When you define a new class, you define the characteristics of all objects of that class, as well as their behaviors. For example, if you create your own windowing operating system, you might want to create screen widgets (known as controls in Windows). One control of interest might be a listbox, a control that 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: height, width, location, and text color, for example. Programmers have also come to expect certain behaviors of listboxes—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.
To define a new type or class, you first declare it and then
define its methods and fields. You declare a class using the class
keyword. The complete syntax is:
[attributes
] [access-modifiers
] classidentifier
[:base-class
] {class-body
}
Attributes are used to provide special metadata about a class (that is, information about the structure or use of the class). You will not need attributes for routine C# programming.
Access modifiers are discussed later in this chapter. (Typically, your
classes will use the keyword public
as an access modifier.)
The identifier is the name of the class that you provide. Typically, C# classes are named with nouns (Dog, Employee, ListBox). The naming convention (not required, but strongly encouraged) is to use Pascal notation. In Pascal notation, you don’t use underbars or hyphens, but if the name has two words (Golden Retriever), you push the two words together, each word beginning with an uppercase letter (GoldenRetriever).
As mentioned earlier, inheritance is one of the pillars of object-oriented programming. The optional base class is explained when inheritance is discussed in Chapter 11.
The member definitions that make up the
class-body are enclosed by open and closed curly braces ({}
):
class Dog { int age; // the dog's age int weight; // the dog's weight Bark( ) { //... } Eat( ) { // ... } }
Methods within the class definition of Dog describe all the things a dog can do. The fields (member variables) such as age and weight describe all the dog’s attributes or state.
To make an actual instance, or object, of the Dog class, you must declare the object and allocate memory for the object. These two steps combined are necessary to create, or instantiate, the object. Here’s how you do it.
First, you declare the object by writing the name of the class
(Dog
) followed by an identifier
(name) for the object or instance of that class:
Dog milo; // declare milo to be an instance of Dog
This is not unlike the way you create a local variable; you
declare the type (in this case, Dog
), followed by the identifier (milo
). Notice also that (as with variables)
by convention, the identifier for the object uses Camel notation.
Camel notation is just like Pascal notation except that the very first
letter is lowercase. Thus, a variable or object name might be myDog
, designatedDriver
, or plantManager
.
The declaration alone doesn’t actually create an instance,
however. To create an instance of a class, you must also allocate
memory for the object using the keyword new
.
milo = new Dog( ); // allocate memory for milo
You can combine the declaration of the Dog
type with the memory allocation into a
single line:
Dog milo = new Dog( );
This code declares milo
to be
an object of type Dog
and also
creates a new instance of Dog
.
You’ll see what the parentheses are for later in this chapter in the
discussion of the constructor.
In C#, everything happens within a class.
No methods can run outside of a class, not even Main( )
. The Main( )
method is the entry point for your program; it is called
by the operating system, and it is where execution of your program
begins. Typically, you’ll create a small class to house Main( )
, because like every method, Main( )
must live within a class. Some of
the examples in this book use a class named Tester
to house Main( )
:
public class Tester { public static void Main( ) { //... } }
Even though Tester
was
created to house the Main( )
method, you’ve not yet instantiated any objects of type Tester
. To do so, you would write:
Tester myTester = new Tester( ); // instantiate an object of type Tester
As you’ll see later in this chapter, creating an instance of the
Tester
class allows you to call
other methods on the object you’ve created (myTester
).
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.
The .NET Framework provides a fully functional DateTime
class. The creation of a
simplified Time
class here is
used only to illustrate how such a class might be designed and
implemented.
You might implement such a class by defining a single method and six variables, as shown in Example 7-1.
Example 7-1. The Time class
using System; public class Time { // private variables private int year; private int month; private int date; private int hour; private int minute; private int second; // public methods public void DisplayCurrentTime( ) { Console.WriteLine( "stub for DisplayCurrentTime" ); } } public class Tester { static void Main( ) { Time timeObject = new Time( ); timeObject.DisplayCurrentTime( ); } }
This code creates a new user-defined type: Time
. The Time
class definition begins with the
declaration of a number of member variables: Year
, Month
, Date
, Hour
, Minute
, and Second
.
The keyword private
indicates
that these values can only be called by methods of this class. The
private
keyword is an access
modifier, explained later in this chapter.
Many C# programmers prefer to put all of the member fields together, either at the very top or the very bottom of the class declaration, though that is not required by the language.
The only method declared within the Time
class is the method DisplayCurrentTime( )
. The DisplayCurrentTime( )
method is defined to return void
; that is, it will not return a value to
the method that invokes it. For now, the body of this method has been
“stubbed out.”
Stubbing out a method is a temporary measure you might use when you first write a program to allow you to think about the overall structure without filling in every detail when you create a class. When you stub out a method body, you leave out the internal logic and just mark the method, perhaps with a message to the console:
public void DisplayCurrentTime( ) { Console.WriteLine( "stub for DisplayCurrentTime"); }
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, named timeObject
:
Time timeObject = new Time( );
Technically, an unnamed instance of Time
is created on the heap and a
reference to that object is returned and used to initialize the
Time
reference named timeObject
. Because that is cumbersome,
we’ll simply say that a Time
instance named timeObject
was
created.
Because timeObject
is an
instance of Time
, Main( )
can make use of the DisplayCur-rentTime( )
method available with
objects of that type and call it to display the time:
timeObject.DisplayCurrentTime( );
You invoke a method on an object by writing the name of the
object (timeObject
) followed by the
dot operator (.
), the method name
(DisplayCurrentTime
), and the
parameter list in parentheses (in this case, empty). You’ll see how to
pass in values to initialize the member variables in the discussion of
constructors, later in this chapter.
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 7-1 summarizes the C# access modifiers .
Table 7-1. Access modifiers
Access modifier | Restrictions |
---|---|
| No restrictions. Members that are
marked |
| The members in class A that are
marked |
| The members in class A that are
marked |
| The members in class A that are
marked |
| The members in class A that are
marked |
Public methods are part of the class’s public interface: they define how this class behaves. Private methods are “helper methods” used by the public methods to accomplish the work of the class. Because the internal workings of the class are private, helper methods need not (and should not) be exposed to other classes.
The Time
class and its method
DisplayCurrentTime( )
are both
declared public
so that any other
class can make use of them. If DisplayCurrentTime( )
had been private
, it would not be possible to invoke
DisplayCurrentTime( )
from any
method of any class other than methods of Time
. In Example 7-1, DisplayCurrentTime( )
was invoked from a
method of Tester
(not Time
), and this was legal because both the
class (Time
) and the method
(DisplayCurrentTime
) were marked
public
.