6. Using Instance Methods

In the last section, you learned how to extract data from and manipulate strings using methods in the String class. The String methods you used were different from the methods in the Convert and Math classes, because they required you to put a string variable before the dot of the method call, instead of using the class name. In this section, you will learn more about how to use instance methods.

6.1. Non-Static Methods

Methods whose interface does not contain the word "static" are called instance methods. Instance methods operate on data in a variable whose name appears before the dot in the method call statement. Instance method call statements have the following form:

result = variable-name . method-name ( parameter-list )

where

  • variable-name is the name of a variable whose data is operated on by the instance method

  • method-name is the name of the instance method

  • parameter-list is a comma-delimited list of parameter values for the method

As you learned in the last section, when calling instance methods in the String class (such as Substring), you must have a string variable before the dot of the method call. But what about instance methods in other classes?

6.2. Objects and Classes

Strings are objects - entities that contain both data as well as code. It's hard to get very far in C# programming without encountering objects, since C# is an object-oriented language, and all variables in C# can be treated as objects.

You work with objects by invoking their instance methods. Recall from chapter 1 that you can think of an object as an independent entity that contains data (called "state") and provides methods that you can invoke to query or manipulate the data. To invoke a method on an object, you write the name of the object, followed by a dot, followed by the name of the instance method you wish to use, like this:

string str = "I am an object.";
str = str.ToUpper();

In object-oriented lingo, you would say this code "invokes the ToUpper method on the str object." The ToUpper method contains instructions to examine the characters inside the object and return a new string with all letters in the original string capitalized. Invoking the ToUpper method on a different string object will usually yield a different result, because a different object will usually contain different state.

Since we work with objects by invoking their methods, it is important to know what methods a given object supports, and how to invoke them properly. For starters, every object can convert itself to a string if you call its ToString() method. For example, you can convert an int value to a string like this:

int weight = 5;
string s = weight.ToString();  // converts i to a string

Now you know two ways to convert a variable to a string: use the Convert.ToString() method, or, using the object-oriented approach, treat the variable as an object and invoke its ToString() method.

You might be wondering what other methods can be invoked on an int variable. It turns out that, although there are a few other instance methods you can use with int variables, none of them are particularly useful for typical programming tasks. In fact, of all the built-in data types in C#, only string has many useful instance methods. (By the way, if you are still curious what methods can be invoked on an int variable, look up the documentation for the System.Int32 struct in the library documentation. The int data type is an alias for the System.Int32 struct, in the same way that the string data type is an alias for the String class.)

Now, let's turn our attention from the built-in data types back to the classes in the C# standard library. Classes and objects are very closely related concepts. You see, classes define the methods that you invoke on objects.

Back in chapter 2, I defined a class to be a group of methods. Actually, classes can contain a group of both methods and variables. But there's more to it than that simple definition might lead you to believe.

Classes serve two very different purposes in C#. One purpose is to group together related methods. Recall from chapter 2 that statements have to be grouped into methods, and methods are grouped into classes. Some C# classes exist only to group together related methods and variables -- the Console, Convert, and Math classes are examples of this. In these classes, all of the methods and variables are marked static. There aren't very many of these classes in the C# Class Library.

The other purpose for classes in C# is to define objects. Most C# classes, including the String class, fall into this category. These classes may have a few static members ("class methods"), but most of their members are usually non-static ("instance methods"), meaning you must have an object to use the method.

Let me try to explain what I mean when I say that the purpose for most C# classes is to define objects. Consider the following code:

string str;
str = "Fruit";
int len = str.ToUpper();

Let's take these three lines of code one at a time.

string str;

The data type of str is string. The variable definition "string str" establishes a relationship between the variable str and the String class (the string data type is an alias for the String class). Specifically, this relationship determines which methods you can invoke on str -- non-static methods ("instance methods") in the String class.

str = "Fruit";

Before you can invoke a method on a variable like str, you must first create an object. This line creates a String object with the text "Fruit," and stores a reference to that object in str. (As was mentioned earlier, string variables do not actually hold object values; instead, they hold a reference to an object.)

str = str.ToUpper();

The compiler allows you to invoke the ToUpper method on str because the ToUpper method is one of the object methods in the String class.

Let's look at another kind of object: a Random object. Random objects have an entirely different set of capabilities than String objects. Random objects contain methods that produce numbers randomly, much like you might pick a number from random out of a bucket containing several pieces of paper with numbers on them. Random objects are very useful in games, where you don't want the computer to do exactly the same thing each time the user plays the game. Random objects are defined by the Random class. Here's a bit of code that uses the Random object:

Random rand;
rand = new Random();
int num = rand.Next(10);

Let's discuss these lines one at a time.

Random rand;

The data type of rand is Random. We will only be able to use methods in the Random class with the rand variable.

rand = new Random();

This may look a little strange, but it is doing exactly what 'str = "fruit"' did above: it creates a Random object and attaches rand to it. We'll discuss the "new Random()" syntax shortly.

int num = rand.Next(10);

Here, one of the methods in the Random class is invoked on the rand object. If you're curious, it randomly chooses a number from 0 to 9 (1 less than the parameter) and returns it. Note that it would not be legal to invoke the ToUpper method we used above with str on rand, because there is no ToUpper method defined in the Random class:

int num = rand.ToUpper();  // BAD: No ToUpper method in the Random class

Let's review what we've covered so far:

  • Objects are software entities that contain data and methods.

  • You use an object by invoking methods on a variable that contains a reference to the object.

  • The methods you may invoke on a given object are the instance methods defined in the object's class (the variable's data type).

6.3. Creating Objects

Before you can use an object, you must create it. Creating an object usually involves two steps:

  1. Define a variable for it.

  2. Instantiate the object.

You already know how to define variables (write the data type followed by the variable name). The second step needs more explanation. "Instantiate" is a fancy term that means "create." Objects do not exist until you create them using a special expression that looks like this:

new class-name ( parameter-list )

You can do this in two steps, as shown in the previous section, but typically you do it in one step:

Random rand = new Random();  // define and create a Random object

How do you know what to put in parenthesis for the parameter list? The C# API is our guide. All classes used to define objects contain a group of special methods called constructors. You can recognize the constructors because they are always listed first, in their own special section; they have no return type; and they are always named with the class name. The Random class contains two constructors:

public Random( )

public Random(int seed)

Usually, a class contains at least one constructor that you can use without having to supply any parameters (called a "parameterless constructor"), and unless you have a good reason to do otherwise, that's the one to use, as I did in the example above.

By the way, remember the rule that the assignment statement requires that the type of the expression be compatible with the data type of the variable on the left? When working with objects, that means you can't do things like this:

string str = new Random();  // ILLEGAL: Random is not assignment compatible with string

Remember that when you define and instantiate an object, you must use the same class name on both sides of the equal sign.

Before I end this section, I do want to mention that you can sometimes obtain an object to use without creating it yourself. Take a look at this familiar statement:

string str = Console.ReadLine();

Here, we're defining a string variable, but we're not creating a String object with new (or by writing something in quotes). Instead, we're calling a method -- ReadLine -- to create one for us. The code in the ReadLine method creates and returns a String object. I bring this up not to confuse the issue, but to explain that it's perfectly legitimate to get an object from a method instead of creating it yourself.

6.4. Objects Applied: Creating Files

Often programs need to write output to files on disk in addition to displaying information on the screen. In C#, you can use the StreamWriter class to do this in three easy steps.

First, define a StreamWriter variable, and instantiate a StreamWriter object to use to write data to the file. The StreamWriter constructor takes the name of the output file as a parameter. For example, to create an output file c:\output.txt, you might write the following line of code:

StreamWriter out = new StreamWriter("c:\\output.txt");

If the output file does not exist, it will be created automatically by the StreamWriter. If the file already exists, the StreamWriter will erase its contents.

After creating the StreamWriter object, call the object's Write() and WriteLine() methods to write one or more lines of data to the file. For example, the following would write two lines of output to the file:

out.WriteLine("Here is line 1");
out.WriteLine("Here is line 2");

Finally, call the StreamWriter object's Close() method to force the computer to physically write all of the data to the drive and finalize the file:

out.Close();

Here is a complete program that uses a StreamWriter to generate a form letter.

Example 4.4. FormLetter.cs

// FormLetter.cs
// Creates a form letter informing a customer that a product 
// is no longer in stock

using System;
using System.IO;

class FileMaker
{
    static void Main()
    {
        Console.Write("Enter the name of the customer: ");
        string name = Console.ReadLine();

        Console.Write("Enter the name of the product: ");
        string product = Console.ReadLine();

        StreamWriter file = new StreamWriter("c:\\letters\\formletter.txt");
        file.WriteLine("Dear " + name + ",");
        file.WriteLine();
        file.WriteLine("We regret to inform you that the " + product + " you ordered");
        file.WriteLine("is no longer in stock. We hope you can find another item");
        file.WriteLine("in our catalog.");
        file.WriteLine("        Sincerely yours,");
        file.WriteLine("        The Company");
        file.Close();

    } // main

} // class FormLetter

Notes about the program:

  • Notice the line

    using System.IO;

    at the top of the program. The StreamWriter class is in the System.IO namespace, not the System namespace. So, a separate using statement is needed to avoid having to fully qualify the StreamWriter class name wherever it is used in the program.

Chapter 9 will have more on the subject of file I/O. But I wanted to introduce the StreamWriter in this section, because it is so easy to use, and is a practical example of objects at work.

6.5. Objects Applied: Creating Graphics

As a final example of the power of objects, I present a program that creates a bitmap drawing.

Example 4.5. MakeDrawing.cs

// MakeDrawing.cs
// Creates a file named mypic.png containing a drawing

using System;
using System.Drawing;

class MakeDrawing
{
    static void Main()
    {
        Bitmap drawing = new Bitmap(400, 300);
        Graphics g = Graphics.FromImage(drawing);

        Font arialFont = new Font("Arial", 24);

        g.DrawRectangle(Pens.Black, 0, 0, 399, 299);
        g.DrawString("Hello there!", arialFont, Brushes.Purple, 100, 50);

        g.DrawEllipse(Pens.DarkOrange, 80, 80, 580, 130);
        g.FillRectangle(Brushes.Blue, 40, 80, 100, 30);
        SolidBrush brush = new SolidBrush(Color.FromArgb(255, 50, 25));
        g.DrawString("Wow!", arialFont, brush, 200, 200);

        drawing.Save("mypic.png", System.Drawing.Imaging.ImageFormat.Png);

    }
}

This program uses a number of objects from classes in the System.Drawing namespace. Notice the using statement at the top of the program that makes those classes available for use in the program.