Initializing Objects - Classes - C# 24-Hour Trainer (2015)

C# 24-Hour Trainer (2015)

Section IV

Classes

Lesson 24

Initializing Objects

Most of the time when you create an object, you need to initialize its properties. You generally wouldn't create an Employee object without at least setting its FirstName and LastName properties. The following code shows how you might initialize an Employee object:

// Make an Employee named Alice.

Employee alice = new Employee();

alice.FirstName = "Alice";

alice.LastName = "Archer";

alice.Street = "100 Ash Ave";

alice.City = "Bugsville";

alice.State = "CO";

alice.Zip = "82010";

alice.EmployeeId = 1001;

alice.MailStop = "A-1";

Though this is relatively straightforward, it is fairly tedious. Creating and initializing a bunch of Employees would take a lot of repetitive code. Fortunately C# provides alternatives that make this task a little easier.

In this lesson you learn how to initialize an object's properties as you create it. You also learn how to define constructors that make initializing objects easier and how to make destructors that clean up after an object.

Initializing Objects

C# provides a simple syntax for initializing an object's properties as you create it. Create the object as usual but follow the new keyword and the class's name with braces. Inside the braces, place comma-separated statements that initialize the object's properties.

For example, the following code creates and initializes an Employee object named alice similar to the one created in the previous code. The statements inside the braces initialize the object's properties.

// Make an Employee named Alice.

Employee alice = new Employee()

{

FirstName = "Alice",

LastName = "Archer",

Street = "100 Ash Ave",

City = "Bugsville",

State = "CO",

Zip = "82010",

EmployeeId = 1001,

MailStop = "A-1",

};

NOTE

Note that an initializer can only initialize properties that the code can access. For example, if a property is private, the initializer cannot set its value.

This may not seem like much of an improvement because it has just as many lines of code as the previous version. (Two more lines if you count the braces.) It is easier to type, however, partly because you don't need to repeatedly type the name of the object.

IntelliSense also helps. When you open the braces and type F, IntelliSense can figure out that you're trying to initialize the FirstName property. At that point, you can press Tab to fill in the rest of the property's name without typing it.

IntelliSense also knows what values you've entered previously and won't show them to you again. For example, if you initialize the Street property and then later type S, IntelliSense knows that you must be initializing the State property.

Constructors

Initializers are handy and easy to use but sometimes you might like some extra control over how an object is created. Constructors give you that extra control.

A constructor is a method that is executed when an object is created. The constructor executes before the code that creates the object gets hold of it. The constructor can perform any setup tasks that are necessary to prepare the object for use. It can look up data in databases, prepare data structures, and initialize properties.

To create a constructor, you make a method that has no return type and that is named after the class. Alternatively, you can think of it as a method that returns the class's type and that has no name. You'll see examples shortly.

The next two sections describe two kinds of constructors: parameterless constructors and parameterized constructors. A later section explains how one constructor can invoke another to avoid duplicated code.

Parameterless Constructors

A constructor can take parameters just like any other method to help it in its setup tasks. A parameterless constructor (sometimes called an empty constructor) takes no parameters, so it's somewhat limited in what it can do.

For example, suppose the Manager class has a DirectReports property, which is a list of Employees that report to a given manager. A parameterless constructor cannot build that list because it doesn't know what employees to put in it. It can, however, initialize theDirectReports property to an empty list, as shown in the following code:

class Manager : Employee

{

public List<Employee> DirectReports;

// Initialize the Manager.

public Manager()

{

DirectReports = new List<Employee>();

}

}

You implicitly invoke a parameterless constructor any time you create an object without using any parameters. For example, the following code creates a new Person object. When this code executes, control jumps briefly to the parameterless constructor so it can prepare the object for use.

Manager fred = new Manager();

Note that C# creates a default public parameterless constructor for you if you don't define any constructors explicitly. If you give the class any constructors, however, C# doesn't create the default constructor. In that case, if you want a parameterless constructor, you must make it yourself.

Parameterized Constructors

Parameterless constructors are useful but fairly limited because they don't have much information to go by. To give a constructor more information, you can make it take parameters just like you can with any other method.

One simple type of parameterized constructor uses its parameters to initialize properties. For example, you could make a constructor for the Person class that takes the person's first and last names as parameters. The constructor could then set the object's FirstNameand LastName properties.

Why would you bother doing this when you could use an initializer? First, the syntax for using a constructor is slightly more concise than initializer syntax. The following code uses a constructor that takes eight parameters to initialize an Employee's properties:

Employee alice = new Employee("Alice", "Archer", "100 Ash Ave",

"Bugsville", "CO", "82010", 1001, "A-1");

Compare this code to the earlier snippet that used initializers. This version is more concise, although it's less self-documenting because it doesn't explicitly list the property names.

The second reason you might prefer to use a parameterized constructor instead of an initializer is that a constructor can perform all sorts of checks that an initializer cannot. For example, a constructor can validate its parameters against each other or against a database. An Employee class's constructor could take an employee ID as a parameter and use a database to verify that the employee really exists.

A constructor can also require that certain parameters be provided. For example, a Person constructor could require that the first and last name parameters be provided. If you rely on initializers, the program could create a Person that has no first or last name.

To make a constructor that takes parameters, simply add the parameters as you would for any other method. The following code shows a constructor for the Person class that uses its parameters to initialize the new Person object's properties:

// Initialize all values.

public Person(string firstName, string lastName, string street,

string city, string state, string zip)

{

FirstName = firstName;

LastName = lastName;

Street = street;

City = city;

State = state;

Zip = zip;

}

Destructors

Constructors execute when a new object is created to perform initialization chores. Destructors execute when an object is being destroyed to perform cleanup chores. For example, a destructor might disconnect from databases, close files, free memory, and do whatever else is necessary before the object gets carted off to the electronic recycle center.

Destructors are simpler than constructors because:

· A class can have only one destructor.

· You cannot call a destructor directly; they are only called automatically.

· A destructor cannot invoke another destructor.

· Destructors cannot take parameters.

· Destructors automatically call base class destructors when they are finished.

To make a destructor, you create a method named after the class with a tilde character (˜) in front of its name. You cannot include an access specifier (such as private or public), return type, or parameters. For example, the following code shows a simple destructor for the Person class:

˜Person()

{

// Perform cleanup chores here…

}

Destructors are a fairly specialized topic and you are unlikely to need to build one until you have more programming experience, but I wanted to describe them for an important reason: so you know when destructors execute and you can help them perform well.

You might think that so far destructors are fairly simple and that would be the end of the story except for one remaining question: “When are destructors called?” This turns out to be a trickier question than you might imagine. To understand when a destructor runs, you need to understand the garbage collector.

Normally a C# program runs merrily along, creating variables and objects as needed. Sometimes all of the references to an object disappear so the program no longer has access to the object. In that case, the memory (and any other resources) used by that object are lost to the program. If the program makes a lot of objects and then discards them in this way, the program will eventually use up a lot of memory.

Eventually the program may start to run out of available memory. At that point, the garbage collector springs into action. The garbage collector runs when it thinks the program may have used a lot of inaccessible memory such as old, discarded Employee objects. When the garbage collector runs, it reclaims the memory lost by objects that are inaccessible and makes that memory available for future objects.

It is only when the garbage collector reclaims an object's memory that the object's destructor executes. So the answer to the question “When are destructors called?” is: “Whenever the garbage collector runs.” So when does the garbage collector run? The answer to this new question is: “Whenever it feels like it.”

The end result is that you cannot really know when a destructor will run. The fancy name for this is non-deterministic finalization. Many programs never run low on memory so the garbage collector doesn't run until the program ends.

The moral of the story is that you can use destructors to clean up after an object but you shouldn't rely on them to handle tasks that must be done in a timely fashion. For example, if a destructor closes a file so other programs can use it, the file may not actually be closed until the program ends.

If you want to perform actions such as this in a timely fashion, give the class a Dispose method that the program can call explicitly to clean up after the object.

NOTE

The IDisposable interface formalizes the notion of providing a Dispose method that cleans up after an object. It's a fairly advanced topic, however, so it isn't covered here. For more information, see msdn.microsoft.com/library/b1yfkh5e.aspx andmsdn.microsoft.com/library/system.idisposable.aspx.

If an object (whether or not you created its class) provides a Dispose method, you should use it when you are done with the object so you can free its resources.

For example, you can use a Graphics object to draw on a bitmap. A Graphics object uses limited system resources, so it's a good practice to call its Dispose method when you're done using it.

Unfortunately, it's easy to forget to call Dispose. To help you remember, C# provides the using statement. The using statement is followed by the object that it manages and, when the using block ends, the program automatically calls the object's Dispose method.

The usual syntax for a using block is:

using (variableInitialization)

{

... Statements ...

}

In this syntax, the variableInitialization declares and initializes the variable that the block controls. (You can declare the object outside of the using block, but putting it inside the block usually makes it easier to read and restricts its scope to the block.)

For example, the following code creates a Graphics object named gr associated with the bitmap bigBitmap. The using block ensures that the program executes the gr object's Dispose method when it finishes the block.

using (Graphics gr = Graphics.FromImage(bigBitmap))

{

// Draw stuff…

}

Note that the object's Dispose method is called even if the program exits from the block because of an exception, a return statement, or some other method.

Invoking Other Constructors

You can give a class many different constructors as long as they have different parameter lists (so C# can tell them apart). For example, you might give the Person class a parameterless constructor, a second constructor that takes first name and last name as parameters, and a third constructor that takes first and last name, street, city, state, and ZIP code as parameters.

Often when you give a class multiple constructors, some of them perform the same actions. In the Person example, the constructor that initializes first name, last name, street, city, state, and ZIP code probably does the same things that the second constructor does to initialize just first and last name.

You can also find overlapping constructor functionality when one class inherits from another. For example, suppose the Person class has FirstName and LastName properties. The Employee class inherits from Person and adds some other properties such as EmployeeId andMailStop. The Person class's constructor initializes the FirstName and LastName properties, something that the Employee class's constructors should also do.

Having several methods perform the same tasks makes debugging and maintaining code harder. Fortunately, C# provides a way you can make one constructor invoke another.

To make one constructor invoke another in the same class, follow the constructor's parameter declarations with a colon and the keyword this, passing this any parameters that the other constructor should receive. For example, the following code shows three constructors for the Person class that invoke each other. The code that invokes other constructors is shown in bold:

// Parameterless constructor.

public Person()

{

// General initialization if needed …

}

// Initialize first and last name.

public Person(string firstName, string lastName)

: this()

{

FirstName = firstName;

LastName = lastName;

}

// Initialize all values.

public Person(string firstName, string lastName, string street,

string city, string state, string zip)

: this(firstName, lastName)

{

Street = street;

City = city;

State = state;

Zip = zip;

}

The first constructor is a parameterless constructor. In this example it doesn't do anything.

The second constructor takes first and last names as parameters. The : this() at the end of the declaration means the constructor should invoke the parameterless constructor when it starts.

The third constructor takes name and address parameters. Its declaration ends with: this(firstName, lastName) to indicate that the constructor should begin by calling the second constructor, passing it the firstName and lastName parameters. (That constructor in turn invokes the parameterless constructor.)

Notice that the third constructor doesn't save the firstName and lastName values. That's handled by the second constructor.

You can use a similar syntax to invoke a parent class constructor by simply replacing the keyword this with the keyword base.

For example, the Employee class inherits from the Person class. The following code shows two of the class's constructors. The code that invokes the Person class constructors is shown in bold:

// Parameterless constructor.

public Employee()

: base()

{

}

// Initialize first and last name.

public Employee(string firstName, string lastName)

: base(firstName, lastName)

{

}

The first constructor is parameterless. It invokes its parent class's parameterless constructor by using : base().

The second constructor takes first and last name parameters and invokes the Person class's constructor that takes two strings as parameters.

NOTE

Notice how the constructors invoke other constructors by using the keyword this or base followed by a parameter list. C# uses the parameter list to decide which constructor to invoke. That's why you cannot have more than one constructor with the same kinds of parameters. For example, if two constructors each took a single string as a parameter, how would C# know which one to use?

Try It

In this Try It, you enhance the Person, Employee, and Manager classes you built for the third Try It in Lesson 23. You add constructors to make initializing objects easier and you add destructors so you can trace object destruction when the program ends. You also build the user interface shown in Figure 24.1 to test the classes' constructors and destructors.

Constructors dialog box presenting six buttons for person, employee, and manager with and without parameters.

Figure 24.1

Lesson Requirements

In this lesson, you:

· Copy the Person, Employee, and Manager classes you built for the third Try It in Lesson 23 (or download the version that's available on the book's website).

· Give the Person class a parameterless constructor. Make it print a message to the Console window indicating that a new Person is being created.

· Give the Person class a constructor that initializes all of the class's properties. Make it invoke the parameterless constructor and also display its own message.

· Give the Person class a destructor that displays a message in the Console window.

· Make similar constructors and destructors for the Employee and Manager classes.

· Build the user interface shown in Figure 24.1. Add code behind the Buttons to create Person, Employee, and Manager objects.

· Run the program, make some objects, and close the program. Study the Console window messages to see if they make sense.

NOTE

You can download the code and resources for this lesson from the website at www.wrox.com/go/csharp24hourtrainer2e.

Hints

· Make the constructors and destructors invoke each other where possible to avoid duplicate work.

· When you use parameterless constructors, use object initialization to set the objects' properties.

Step-by-Step

· Copy the Person, Employee, and Manager classes you built for the third Try It in Lesson 23 (or download the version that's available on the book's website).

1. This is relatively straightforward.

· Give the Person class a parameterless constructor. Make it print a message to the Console window indicating that a new Person is being created.

1. The Person class's parameterless constructor should look something like this:

2. public Person()

3. {

4. Console.WriteLine("Person()");

}

· Give the Person class a constructor that initializes all of the class's properties. Make it invoke the parameterless constructor and also display its own message.

1. This constructor should look something like this:

2. public Person(string firstName, string lastName,

3. string street, string city, string state, string zip)

4. : this()

5. {

6. FirstName = firstName;

7. LastName = lastName;

8. Street = street;

9. City = city;

10. State = state;

11. Zip = zip;

12. Console.WriteLine("Person(parameters)");

}

· Give the Person class a destructor that displays a message in the Console window.

1. This destructor should look something like this:

2. ˜Person()

3. {

4. Console.WriteLine("˜Person");

}

· Make similar constructors and destructors for the Employee and Manager classes.

1. The following code shows the Employee class's constructors and destructor:

2. public Employee()

3. : base()

4. {

5. Console.WriteLine("Employee()");

6. }

7. public Employee(int employeeId, string mailStop,

8. string firstName, string lastName, string street,

9. string city, string state, string zip)

10. : base(firstName, lastName, street, city, state, zip)

11. {

12. EmployeeId = employeeId;

13. MailStop = mailStop;

14. Console.WriteLine("Employee(parameters)");

15. }

16. ˜Employee()

17. {

18. Console.WriteLine("˜Employee");

}

19.The following code shows the Manager class's constructors and destructor:

20. public Manager()

21. : base()

22. {

23. DirectReports = new List<Employee>();

24. Console.WriteLine("Manager()");

25. }

26. public Manager(string departmentName, int employeeId,

27. string mailStop, string firstName, string lastName,

28. string street, string city, string state, string zip)

29. : base(employeeId, mailStop, firstName, lastName, street,

30. city, state, zip)

31. {

32. DepartmentName = departmentName;

33. Console.WriteLine("Manager(parameters)");

34. }

35. ˜Manager()

36. {

37. Console.WriteLine("˜Manager");

}

· Build the user interface shown in Figure 24.1. Add code behind the Buttons to create Person, Employee, and Manager objects.

1. This is relatively straightforward.

· Run the program, make some objects, and close the program. Study the Console window messages to see if they make sense.

1. The following text shows the program's output if you create a Manager with parameters and then close the program. I've removed some messages generated by the program itself showing when various threads exited.

2. Creating a Manager with parameters

3. Person()

4. Person(parameters)

5. Employee(parameters)

6. Manager(parameters)

7. ˜Manager

8. ˜Employee

˜Person

9. When I clicked the Manager w/Parameters Button, the program performed the ­following actions:

a. The Button's Click event handler displayed the message “Creating a Manager with parameters.” It then called the Manager class's parameterized constructor.

b. That constructor invoked the parameterized Employee constructor.

c. That constructor invoked the parameterized Person constructor.

d. That constructor invoked the parameterless Person constructor.

e. That constructor displayed the message “Person()” and returned control to the parameterized Person constructor that called it.

f. The parameterized Person constructor displayed the message “Person(parameters)” and returned control to the parameterized Employee constructor that called it.

g. The parameterized Employee constructor displayed the message “Employee(parameters)” and returned control to the parameterized Manager ­constructor that called it.

h. The parameterized Manager constructor displayed the message “Manager(parameters)” and returned control to the Button's Click event handler.

10.When the Button's Click event handler ended, the Manager object it created went out of scope so it was lost to the program. It wasn't destroyed, however, because the garbage collector didn't think it needed to run. (The program undoubtedly had plenty of memory left over.) It only ran when I closed the program. At that point, the program performed the following actions:

a. The program was ending, so the garbage collector ran. It called the Manager object's destructor.

b. That destructor displayed a message and then automatically called its base class destructor in the Employee class.

c. That destructor displayed a message and then automatically called its base class destructor in the Person class.

d. That destructor displayed a message.

Exercises

1. Copy the program you built for Exercise 23-1 (or download the version that's available on the book's website) and change the main program so it uses initializers to prepare its ComplexNumber objects. Be sure to update new instances created inside the ComplexNumberclass.

2. Copy the program you built for Exercise 1 and give the ComplexNumber class a constructor that initializes the new number's real and imaginary parts. Modify the program to use this constructor.

3. Copy the program you built for Lesson 23's second Try It (or download the TryIt23b ­program from the book's website) and give the BankAccount class a constructor that guarantees that you cannot create an instance with an initial balance under $10. Change the main program so it uses this constructor.

4. Make a MemoryWaster class that has two fields: an integer named Megabytes and an array of bytes named Bytes. Give the class a constructor that takes a number of megabytes as a parameter, saves that value in the Megabytes field, allocates the array to hold that amount of memory, and writes a message in the Console window saying how many megabytes it is allocating. (Don't forget to multiply by 1,024 × 1,024 to convert megabytes to bytes.)

Also give the class a destructor that writes a message in the Console window saying how many megabytes it is freeing.

Finally, make a user interface that lets the user enter a number of megabytes and click a Button to create a MemoryWaster. Use the program to allocate memory until the garbage collector runs. For example, on my system I can allocate a 500 MB MemoryWaster, but when I try to allocate a second, the garbage collector reclaims the first one. (Hint: You may want to protect the Button's event handler with a try-catch block. For example, try making a 10,000 MB MemoryWaster.)

5. [Graphics] Create a Shape class that has three properties: a Pen, a Brush, and a Rectangle. (Hint: Include the statement using System.Drawing in the class's file.)

Give the class two initializing constructors. The first should have the following signature:

public Shape(Pen pen, Brush brush, int x, int y, int width, int height)

Make the constructor use its parameters to initialize its properties.

The second constructor should have the following signature:

public Shape(Pen pen, Brush brush, Point location, Size size)

Make this constructor invoke the first one.

Also give the class a Draw method that takes a Graphics object as a parameter and uses it to draw the shape's bounding rectangle with the Shape's pen and brush. Make the main program create several Shape objects and draw them in a PictureBox's Paint event handler.

6. [Graphics] Copy the program you wrote for Exercise 24.5 and add an Ellipse class that inherits from Shape. Give it two constructors that invoke the corresponding base class ­constructors. Make the main program create a few instances of the new class. (The Ellipseclass inherits the Shape class's Draw method so the Ellipses will be drawn as rectangles on the PictureBox. Don't worry about that. We'll fix that in the next lesson's exercises.)

7. [Graphics, Hard] Copy the program you wrote for Exercise 24.6 and add a Circle class that inherits from Ellipse. Give the new class a constructor with the following signature:

public Circle(Pen pen, Brush brush, Point center, int radius)

Make this constructor initialize the object by invoking a parent class constructor.

8. [Graphics] Copy the program you wrote for Exercise 24.7 and modify it so the Shape class stores two Color properties named Foreground and Background instead of a Pen and a Brush. Also add a new integer Thickness property and corresponding parameters to the class's constructors. (You'll need to make similar changes to the classes that inherit from Shape.)

Modify the Draw method so it fills and draws the Shape with the appropriate colors and line thickness. To fill the Shape, create a new SolidBrush object. To outline the Shape, create a new Pen object. Include using statements to automatically dispose of the brush and pen.

Finally, update the main program to use the new constructors and make some sample shapes with different colors and line thicknesses.

9. [Graphics] Copy the program you wrote for Exercise 24.8 and modify the Draw method so it uses dashed lines. To do that, set the Pen object's DashStyle property to System.Drawing.Drawing2D.DashStyle.Dash. (This is the only way you can make dashed pens. The stockPen objects such as Pens.Blue and Pens.Chartreuse are solid and one pixel wide.)

NOTE

Please select the videos for Lesson 24 online at www.wrox.com/go/csharp24hourtrainer2evideos.