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.
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
] classidentifier
[:[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( );
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 |
private | The members in class A that are marked |
protected | The members in class A that are marked |
internal | The members in class A that are marked |
protected internal | The members in class A that are marked |
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.
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.
[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.
Get Programming C# 3.0, 5th 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.