Chapter 7. Classes and Objects

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] class identifier [: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.

Instantiating Objects

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.

You might implement such a class by defining a single method and six variables, as shown in Example 7-1.

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.

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

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 .

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.

The behavior of a class is defined by the methods of that class. To make your methods as flexible as possible, you can define parameters : information passed into the method when the method is invoked. Thus, rather than having to write one method when you want to sort your listbox from A to Z and a second method when you want to sort it from Z to A, you define a more general Sort( ) method and pass in a parameter specifying the order of the sort.

Methods can take any number of parameters . The parameter list follows the method name and is enclosed in parentheses. Each parameter’s type is identified before the name of the parameter.

For example, the following declaration defines a method named MyMethod( ) that returns void (that is, it returns no value at all) and 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 7-2 illustrates how you pass values into a method; in this case, values of type int and float.

Here is the output:

    Here are the parameters received: 5, 3.14

The method SomeMethod( ) takes two parameters, firstParam and secondParam, and displays them using Console.WriteLine( ). FirstParam is an int, and secondParam is a float. These parameters are treated as local variables within SomeMethod( ). You can manipulate these values within the method, but they go out of scope and are destroyed when the method ends.

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.

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

     Time timeObject = new Time( );

In fact, a member method is invoked whenever you instantiate an object. This method is called a constructor. Each time you define a class, you are free to define your own constructor, but if you don’t, the compiler will provide one for you invisibly and automatically.

The job of a constructor is to create an instance of the object specified by a class and to put it into a valid state. Before the constructor runs, the object is just a blob of memory; after the constructor completes, the memory holds a valid instance of the class.

The Time class of Example 7-2 does not define a constructor, so the compiler implicitly provides one. The constructor provided by the compiler creates the object but takes no other action.

If you do not explicitly initialize your member variables, they are initialized to innocuous values (integers to 0, strings to the empty string, etc.). Table 7-2 lists the default values assigned to various types.

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 7-3, you want to pass in the current year, month, date, and so forth, so that the object is created with meaningful data.

You declare a constructor like any other member method, except:

  • The name of the constructor must be the same as the name of the class.

  • Constructors have no return type (not even void).

If there are arguments to be passed, you define an argument list just as you would for any other method. Example 7-3 declares a constructor for the Time class that accepts six arguments, one each for the year, month, date, hour, minute, and second for the new Time object you are creating.

The output looks like this:

    8/1/2008 9:35:20

In this example, the constructor takes a series of integer values and initializes all the member variables based on these parameters.

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 each member variable that isn’t assigned-to by you is initialized by the compiler to zero. Integer member variables are set to zero if you don’t otherwise assign them. Remember that value types (such as integers) must be initialized; if you don’t tell the constructor what to do, it sets innocuous values.

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 your Time class to use an initializer so that the value of Second is always initialized, as shown in Example 7-4.

The output looks like this:

    8/1/2008 9:35:30

If you do not provide a specific initializer, the constructor initializes each integer member variable to zero (0). In the case shown, however, the Second member is initialized to 30:

    private int Second = 30; // initializer

Later in this chapter, you will see that you can have more than one constructor. If you assign 30 to Second in more than one of these, you can avoid the problem of having to keep all the constructors consistent with one another by initializing the Second member, rather than assigning 30 in each of the constructors.

The keyword this refers to the current instance of an object. The this reference is a hidden parameter in every nonstatic method of a class (static methods are discussed later in this chapter). There are three ways in which the this reference is typically used. The first way is to qualify instance members that have the same name as 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 ambiguity. While this.hour refers to the member variable, hour refers to the parameter.

You can, for example, use the this reference to make assigning to a field more explicit:

    public void SetTime(
        year, month, date, newHour, newMinute, newSecond)
    {
         this.year = year;         // use of "this" required
         this.month = month;       // required
         this.date = date;         // required
         this.hour = hour;         // use of "this" optional
         this.minute = newMinute;  // optional
         second = newSecond;       // also ok
    }

If the name of the parameter is the same as the name of the member variable, then you must use the this reference to distinguish between the two, but if the names are different (such as newMinute and newSecond), then the use of the this reference is optional.

The second use of the this reference is to pass the current object as a parameter to another method, as in the following code:

    Class SomeClass
    {
         public void FirstMethod(OtherClass otherObject)
         {
              otherObject.SecondMethod(this);
         }
         // ...
    }

This code snippet establishes two classes, SomeClass and OtherClass. SomeClass has a method named FirstMethod( ), and OtherClass has a method named SecondMethod( ).

Inside FirstMethod( ), we’d like to invoke SecondMethod( ), passing in the current object (an instance of SomeClass) for further processing. To do so, you pass in the this reference, which refers to the current instance of SomeClass.

The third use of this is with indexers, which are covered in Chapter 12.

Static and Instance Members

The fields, properties, and methods of a class can be either instance members or static members . Instance members are associated with instances of a type, while static members are associated with the class and not with any particular instance. Methods are instance methods unless you explicitly mark them with the keyword static.

The vast majority of methods will be instance methods. The semantics of an instance method are that you are taking an action on a specific object. From time to time, however, it is convenient to be able to invoke a method without having an instance of the class, and for that, you will use a static method.

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 objects of that class named btnUpdate and btnDelete.

Suppose that the Button class has an instance method Draw( ) and a static method GetButtonCount( ). The job of Draw( ) is to draw the current button, and the job of GetButtonCount( ) is to return the number of buttons currently visible on the form.

You access an instance method through an instance of the class—that is, through an object:

    btnUpdate.SomeMethod( );

You access a static method through the class name, not through an instance:

    Button.GetButtonCount( );

Invoking Static Methods

Static methods are said to operate on the class, rather than on an instance of the class. They do not have a this reference, as there is no instance to point to.

Static methods cannot directly access nonstatic members. You will remember that Main( ) is marked static. For Main( ) to call a nonstatic method of any class, including its own class, it must instantiate an object.

For the next example, use Visual Studio 2005 to create a new console application named StaticTester. VS.NET creates a namespace StaticTester and a class named Class1. Rename Class1 to Tester. Get rid of all the comments and the attribute [STATThread] that Visual Studio .NET puts above Main( ). Delete the args parameter to Main( ). When you are done, your source code should look like this:

    using System;
    namespace StaticTester
    {
         class Tester
         {
             static void Main( )
             {
             }
         }
    }

That is a good starting point. Until now, you’ve always done all the work of the program right in the Main( ) method, but now you’ll create an instance method, Run( ). The work of the program will now be done in the Run( ) method, rather than in the Main( ) method.

Within the class, but not within the Main( ) method, declare a new instance method named Run( ). When you declare a method, you write the accessor (public), followed by the return type, the identifier, and then parentheses:

    public void Run( )

The parentheses will hold parameters, but Run( ) won’t have any parameters, so you can just leave the parentheses empty. Create braces for the method, and within the braces, just print “Hello World” to the console:

    public void Run( )
    {
     Console.WriteLine("Hello world");
    }

Run( ) is an instance method. Main( ) is a static method and cannot invoke Run( ) directly. You will therefore create an instance of the Tester class and call Run( ) on that instance:

    Tester t = new 
 Tester( );

When you type the keyword new, IntelliSense tries to help you with the class name. You’ll find that Tester is in the list; it is a legitimate class like any other.

On the next line, invoke Run( ) on your Tester object t. When you type t followed by the dot operator, IntelliSense presents all the public methods of the Tester class, as shown in Figure 7-1.

When your program is complete, it looks like Example 7-5.

The output looks like this:

    Hello world

This is the model you’ll use from now on in most console applications. The Main( ) method will be limited to instantiating an object and then invoking the Run( ) method.

A common use of static member variables, or fields, is to keep track of the number of instances/objects that currently exist for your class. In the next example, you create a Cat class. The Cat class might be used in a pet-store simulation.

For this example, the Cat class has been stripped to its absolute essentials. The complete listing is shown in Example 7-6. An analysis follows the example.

Here is the output:

    0 cats adopted
    Frisky is 5 pounds
    1 cats adopted
    Whisky is 7 pounds
    2 cats adopted

The Cat class begins by defining a static member variable, instances, that is initialized to zero. This static member field will keep track of the number of Cat objects created. Each time the constructor runs (creating a new object), the instances field is incremented.

The Cat class also defines two instance fields: name and weight. These track the name and weight of each individual Cat object.

The Cat class defines two methods: HowManyCats( ) and TellWeight( ). HowManyCats( ) is static. The number of Cats is not an attribute of any given Cat; it is an attribute of the entire class. TellWeight( ) is an instance method. The name and weight of each cat is per instance (each Cat has his own name and weight).

The Main( ) method accesses the static HowManyCats( ) method directly, through the class:

    Cat.HowManyCats( );

Main( ) then creates an instance of Cat and accesses the instance method, TellWeight( ), through the instance of Cat:

    Cat frisky = new Cat( )
    frisky.TellWeight( );

Each time a new Cat is created, HowManyCats( ) reports the increase.

Unlike many other programming languages (C, C++, Pascal, etc.), C# provides garbage collection . Your objects are destroyed after you are done with them. You do not need to worry about cleaning up after your objects unless you use unmanaged or scarce resources. An unmanaged resource is an operating-system feature outside of the .NET Framework; a scarce resource is a resource that you have in limited quantity, perhaps because of licensing limitations (such as database connections).

If you do control an unmanaged resource, you need to explicitly free that resource when you are done with it. Implicit control over this resource is provided with a destructor, which is called by the garbage collector when your object is destroyed.

You declare a C# destructor with a tilde, as follows:

    ~MyClass( ){}

This syntax is actually translated by the compiler into:

    protected override void Finalize()
    {
       try
       {
           // do work here
       }
       finally
       {
            base.Finalize();
       }
    }

For this reason, some programmers refer to the destructor as a finalizer.

It is not legal to call a destructor explicitly—your destructor (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. (You will learn more about interfaces in Chapter 13.)

The IDisposable interface requires you to create a method named Dispose( ) , which will be called by your clients.

If you provide a Dispose( ) method, you should stop the garbage collector from calling your object’s destructor. To stop the garbage collector, call the static method GC.SuppressFinalize( ) , passing in the this reference for your object. Your destructor 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 destructor,
                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 destructor." );
       }
    }

For some objects, you’d rather have your clients call the Close( ) method. (For example, Close( ) makes 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( ).

Because you cannot be certain that your user will call Dispose( ) reliably, and because finalization is nondeterministic (that is, you can’t control when the garbage collector will run), C# provides a using statement to ensure that Dispose( ) is called at the earliest possible time. The idiom is to declare which objects you are using and then to create a scope for these objects with curly braces. When the close brace is reached, the Dispose( ) method will be called on the object automatically, as illustrated here:

    using System.Drawing;
    class Tester
    {
     public static void Main( )
     {
         using (Font theFont = new Font("Arial", 10.0f))
         {
         // use the font
         }
     }
    }

Because Windows only lets you have a small number of Font objects, we want to dispose of it at the earliest opportunity. In this code snippet, the Font object is created within the using statement. When the using statement ends, Dispose( ) is guaranteed to be called on the Font object.

Objects created within methods are called local variables . They are local to the method, as opposed to belonging to the object, as member variables do. The object is created within the method, used within the method, and then destroyed when the method ends. Local objects are not part of the object’s state—they are temporary value holders, useful only within the particular method.

Local variables of intrinsic types such as int are created on a portion of memory known as the stack. The stack is allocated and de-allocated as methods are invoked. When you start a method, all the local variables are created on the stack. When the method ends, local variables are destroyed.

These variables are referred to as local because they exist (and are visible) only during the lifetime of the method. They are said to have local scope . When the method ends, the variable goes out of scope and is destroyed.

C# divides the world of types into value types and reference types. Value types are created on the stack. All the intrinsic types (int, long) are value types (as are structs, discussed later in the chapter), and thus are created on the stack.

Classes, on the other hand, are reference types. Reference types are created on an undifferentiated block of memory known as the heap. When you declare an instance of a reference type, what you are actually declaring is a reference, which is a variable that refers to another object. The reference acts like an alias for the object.

That is, when you write:

    Dog milo = new Dog( );

the new operator creates a Dog object on the heap and returns a reference to it. That reference is assigned to milo. Thus, milo is a reference object that refers to a Dog object on the heap. It is common to say that milo is a reference to a dog, or even that milo is a Dog object, but technically that is incorrect. milo is actually a reference object that refers to an (unnamed) Dog object on the heap.

The reference milo acts as an alias for that unnamed object. For all practical purposes, however, you can treat milo as if it were the Dog object itself.

The implication of using references is that you can have more than one reference to the same object. To see this difference between creating value types and reference types, examine Example 7-7. A complete analysis follows the output.

The output looks like this:

    firstInt: 5 secondInt: 5
    firstInt: 5 secondInt: 7
    Milo: 5, fido: 5
    Milo: 7, fido: 7

The program begins by creating an integer, firstInt, and initializing it with the value 5. The second integer, secondInt, is then created and initialized with the value in firstInt. Their values are displayed as output:

    firstInt: 5 secondInt: 5

These values are identical. Because int is a value type, a copy of the firstInt value is made and assigned to secondInt; secondInt is an independent second variable, as illustrated in Figure 7-2.

Then the program assigns a new value to secondInt:

    secondInt = 7;

Because these variables are value types, independent of one another, the first variable is unaffected. Only the copy is changed, as illustrated in Figure 7-3.

When the values are displayed, they are different:

    firstInt: 5 secondInt: 7

Your next step is to create a simple Dog class with only one member variable (field) called weight. Note that this field is given a keyword, public, which specifies that any method of any class can access this field. public is what is known as an access modifier. (Generally, you will not make member variables public. The weight field was made public to simplify this example.) Access modifiers are covered in detail later in this chapter.

You instantiate a Dog object and save a reference to that dog in the reference milo.

    Dog milo = new Dog( );

You assign the value 5 to milo’s weight field:

    milo.weight = 5;

You commonly say that you’ve set milo’s weight to 5, but actually you’ve set the weight of the unnamed object on the heap to which milo refers, as shown in Figure 7-4.

Next, you create a second reference to Dog and initialize it by setting it equal to milo. This creates a new reference to the same object on the heap.

    Dog fido = milo;

Notice that this is syntactically similar to creating a second int variable and initializing it with an existing int, as you did before:

    int secondInt = firstInt;
    Dog fido = milo;

The difference is that Dog is a reference type, so fido is not a copy of milo—it is a second reference to the same object to which milo refers. That is, you now have an object on the heap with two references to it, as illustrated in Figure 7-5.

When you change the weight of that object through the fido reference:

    fido.weight = 7;

you change the weight of the same object to which milo refers. The output reflects this:

    Milo: 7, fido: 7

It isn’t that fido is changing milo; it is that by changing the (unnamed) object on the heap to which fido refers, you simultaneously change the value of milo because they refer to the same unnamed object.

If you need a class that acts as a value object, you can create a struct (see the "Structs" sidebar). The use of structs is so unusual that they are not covered beyond the sidebar for the rest of this book. In five years of professional C# programming, my principal use of structs has been to teach what they are, not to actually use them.

  • Members marked public have no restrictions, and are visible to methods of any class.

  • Members marked private are only visible to methods within the same class.

  • Members marked protected are visible to methods within the same class, and methods in derived classes.

  • A constructor is a special method invoked when a new object is created. If you do not define any constructors at all for your class, the compiler will provide a default constructor that does nothing. A default constructor is a constructor that takes no parameters. You are free to create your own default constructor for your class.

  • You can initialize the values of your member variables when you define them in your class.

  • The this keyword is used to refer to the current instance of an object.

  • Every non-static method of a class has an implicit “this” variable passed into the method.

  • Static members are associated with the class itself, not with a particular instance. Static members are declared with the keyword static, and are invoked through the class name. Static methods do not have a this parameter because there is no instance to refer to.

  • C# does not specifically require a destructor method in your classes because the framework will destroy any object that is not in use.

  • You should provide a Dispose( ) method if your class uses unmanaged resources.

  • Local value type variables are created on the stack. When the method ends, these variables go out of scope and are destroyed.

  • Objects are references types, and are created on the heap. When you declare an instance of a reference type, you are actually creating a reference to that object’s location in memory. If you declare a reference to an object on the heap within a method, when the method ends, that reference is destroyed. If there are no remaining references to the object on the heap, the object itself is destroyed by the garbage collector.

Quiz

Question 7–1.

What is the difference between a class and an object?

Question 7–2.

Where are reference types created?

Question 7–3.

Where are value types created?

Question 7–4.

What does the keyword private do?

Question 7–5.

What does the keyword public do?

Question 7–6.

What method is called when you create an object?

Question 7–7.

What is a default constructor?

Question 7–8.

What types can a constructor return?

Question 7–9.

How do you initialize the value of a member variable in a class?

Question 7–10.

What does the keyword this refer to?

Question 7–11.

What is the difference between a static method and an instance method?

Question 7–12.

What does the using statement do?

Exercises

Exercise 7-1.

Write a program with a Math class that has four methods: Add, Subtract, Multiply, and Divide, each of which takes two parameters. Call each method from Main( ).

Exercise 7-2.

Modify the program from Exercise 7-1 so that you do not have to create an instance of Math to call the four methods.