In Chapter 3, I 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, and to use the programmer-defined types that Microsoft has provided in the Framework to facilitate creating applications without having to “reinvent the wheel” to accomplish common tasks such as interacting with the user, databases, web sites, and so forth.
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.
ADogclass 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 in a single, self-contained, and self-sustaining unit of code. When you want to sort the contents of an instance of a Windows listbox control, for example, you tell the listbox to sort itself. How it does so is of no concern to anyone but the person writing the listbox control; that the list-box can be sorted is all any other programmer needs to know. Encapsulation (the idea that an object is self-contained), along with polymorphism and inheritance (explained in just a moment), 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] class identifier [:[base-class [,interface(s)]] {class-body}
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) keywordclasswhich 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,publicis the access modifier,Dogis the identifier, andMammalis the base class.
Attributes are covered in Chapter 8; access modifiers are discussed in the next section. (Typically, your classes will use the keywordpublicas an access modifier.) Theidentifieris the name of the class that you provide. The optionalbase-classis discussed in Chapter 5. The member definitions that make up theclass-bodyare enclosed by open and closed curly braces ({}).
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.
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 intand a variable of typeint. 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 typeint).
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. Such a class might have properties namedHeight,Width,Location, andTextColor, and member methods namedSort(), Add(),Remove(), and so on.
You can’t assign data to theListBoxclass. Instead, you must first create an object of that type, as in the following code snippet:
ListBox myListBox; // instantiate a ListBox object
Once you create an instance ofListBox, you can assign data to it through its properties, and you can call its methods:
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
#region Using directives
using System; using System.Collections.Generic; using System.Text;
#endregion
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(); } } }
You will receive warnings when you compile this class that the member variables ofTime (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 theTimeclass, and if this were a real class, we would make use of these members in other methods.
The only method declared within theTimeclass definition isDisplayCurrentTime(). The body of the method is defined within the class definition itself. Unlike other languages (such as C++), C# doesn’t require that methods be declared before they are defined, nor does the language support placing its declarations into one file and its code into another. (C# has no header files.) All C# methods are defined inline as shown in Example 4-1 with DisplayCurrentTime().
TheDisplayCurrentTime()method is defined to returnvoid; 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. TheTime class definition ends with the declaration of a number of member variables:Year,Month,Date,Hour,Minute, andSecond.
After the closing brace, a second class, Tester, is defined.Testercontains our now familiarMain()method. InMain(), an instance ofTimeis created and its address is assigned to objectt. Becausetis an instance ofTime,Main()can make use of theDisplayCurrentTime()method available with objects of that type and call it to display the time:
An access modifier determines which class methods of other classes can see and use a member variable or method within this class. Table 4-1 summarizes the C# access modifiers.
Table 4-1. Access modifiers
Access modifier
Restrictions
public
No restrictions. Members markedpublicare visible to any method of any class.
private
The members in classAthat are markedprivateare accessible only to methods of classA.
protected
The members in classAthat are markedprotectedare accessible to methods of classAand to methods of classes derived from classA.
internal
The members in classAthat are markedinternalare accessible to methods of any class in A’s assembly.
protected internal
The members in classAthat are markedprotected internalare accessible to methods of classA, to methods of classes derived from classA, and to any class inA’s assembly. This is effectivelyprotectedORinternal. (There is no concept ofprotectedANDinternal.)
It is generally desirable to designate the member variables of a class asprivate. This means that only member methods of that class can access their value. Becauseprivateis the default accessibility level, you don’t 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;
TheTesterclass andDisplayCurrentTime()method are both declaredpublicso that any other class can make use of them.
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 declaredprivateby default, making their access explicit indicates a conscious decision and is self-documenting.
Methods can take any number of parameters.* 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(i.e., which returns no value at all), and which takes two parameters—an integer and a button:
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 typeintandfloat.
Example 4-2. Passing values into SomeMethod( )
#region Using directives
using System; using System.Collections.Generic; using System.Text;
#endregion
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 methodSomeMethod()takes anintand afloatand displays them usingConsole.WriteLine(). The parameters, which are namedfirstParamandsecondParam, are treated as local variables withinSomeMethod().
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 andpi) are created and initialized. These variables are passed as the parameters toSomeMethod(). The compiler mapshowManyPeopletofirstParamandpitosecondParam, based on their relative positions in the parameter list.
Please check back tomorrow for the second part of this article.