Using Enumerations and Structures - Variables and Calculations - C# 24-Hour Trainer (2015)

C# 24-Hour Trainer (2015)

Section II

Variables and Calculations

Lesson 17

Using Enumerations and Structures

The data types you've learned about so far hold strings, integers, dates, and other predefined kinds of information, but sometimes it would be nice to define your own data types.

An enumeration (or enumerated type) lets you define a new data type that can take only one of an allowed list of values. For example, a menu program might define a MealType data type that can hold the values Breakfast, Lunch, and Dinner.

The data types described in previous lessons also can hold only a single piece of data: a name, street address, city, or whatever. Sometimes it would be nice to keep related pieces of data together. Instead of storing a name, address, and city in separate strings, you might like to store them as a single unit.

A structure (sometimes called a struct) lets you define a group of related pieces of data that should be kept together.

In this lesson, you learn how to define and use enumerations and structures to make your code easier to write, understand, and debug.

Enumerations

Defining an enumeration is easy. The following code defines a ContactMethod enumeration that can hold the values None, Email, Phone, or SnailMail:

// Define possible contact methods.

enum ContactMethod

{

None = 0,

Email,

Phone,

SnailMail,

}

NOTE

The final comma in this example is optional. You don't need it because there is no value after SnailMail, but C# allows you to use it if you want to make the lines of code more consistent.

Internally an enumeration is stored as an integral data type, by default an int. An optional number after a value tells C# explicitly which integer to assign to that value. In the preceding code, None is explicitly assigned the value 0.

If you don't specify a value for an enumeration's item (and often you don't care what these values are), its value is one greater than the previous item's value (the first item gets value 0). In this example, None is 0, Email is 1, Phone is 2, and SnailMail is 3.

You create an instance of an enumerated type just as you make an instance of a primitive type such as int, decimal, or string. The following code declares a variable of type ContactMethod, assigns it the value ContactMethod.Email, and then displays its value in the Output window:

ContactMethod contactMethod = ContactMethod.Email;

Console.WriteLine(contactMethod.ToString());

An enumeration's ToString method returns the value's name, in this case “Email.”

Structures

Defining a structure is just as easy as defining an enumeration. The following code defines a simple structure named Address that holds name and address information:

// Define a structure to hold addresses.

struct Address

{

public string Name;

public string Street;

public string City;

public string State;

public string Zip;

public string Email;

public string Phone;

public ContactMethod PreferredMethod;

}

Inside the braces, the structure defines the bits of data that it holds together. The public keywords in this example mean that the fields inside the structure (Name, Street, and so on) are visible to any code that can see an Address.

Notice that the structure can use an enumeration. In this example, the Address structure's PreferredMethod field has type ContactMethod.

In many ways structures behave like simple built-in types such as int and float. In particular, when you declare a variable with a structure type, the code not only declares it but also creates it. That means you don't need to use the new keyword to create an instance of a structure.

After defining the variable, you can access its fields using syntax similar to the way you access a control's properties. Start with the variable's name, follow it with a dot, and then add the field's name.

The following code creates and initializes a new Address structure named homeAddress:

Address homeAddress;

homeAddress.Name = nameTextBox.Text;

homeAddress.Street = streetTextBox.Text;

homeAddress.City = cityTextBox.Text;

homeAddress.State = stateTextBox.Text;

homeAddress.Zip = zipTextBox.Text;

homeAddress.Email = emailTextBox.Text;

homeAddress.Phone = phoneTextBox.Text;

homeAddress.PreferredMethod =

(ContactMethod)preferredMethodComboBox.SelectedIndex;

This code fills in the text fields using values entered by the user in TextBoxes.

The final field is a ContactMethod enumeration. The user selects a value for this field from the preferredMethodComboBox. The code takes the index of the ComboBox's selected item, converts it from an integer into a ContactMethod, and saves the result in the structure'sPreferredMethod field.

NOTE

To correctly convert a ComboBox selection into an enumeration value, the ComboBox must display the choices in the same order in which they are defined by the enumeration. In this example, the ComboBox must contain the items None, Email, Phone, and SnailMail in that order to match up with the enumeration's items.

Structures Versus Classes

In many ways structures are very similar to classes. Lesson 23 says a lot more about classes and the sorts of things you can do with them, and many of the same techniques apply to structures.

For example, both can contain properties, methods, and events. Both can also have constructors, special methods that are executed when you use new to create a new instance. These are described in greater detail in Lesson 23.

While structures and classes have many things in common, they also have some significant differences. A lot of these differences are outside the scope of this book, so I won't cover them here, but one very important difference that you should understand is that structures are value types while classes are reference types.

Reference Types

A reference type doesn't actually hold the data for a class instance. Instead it holds a reference to an instance. The reference is like an address that points to where the data is actually stored.

For example, the following code creates a NewUserForm and displays it:

NewUserForm userForm;

userForm = new NewUserForm();

userForm.ShowDialog();

The first statement declares a variable of type NewUserForm. Initially that variable doesn't refer to anything so if you tried to display the form at this point, the program would crash.

The second statement creates a new instance of the NewUserForm type and saves a reference to the new form in the userForm variable.

Now the variable refers to an instance of the NewUserForm type, so the third statement can safely display that form.

Value Types

In contrast to reference types, a value type actually contains its data instead of refers to it. Many of the primitive data types such as int, double, and decimal are value types.

The following code creates and uses a variable with the Address structure type described earlier:

Address homeAddress;

homeAddress.Name = "Benjamin";

When the code executes the first statement, the program creates the Address structure so it's all ready to go, although its fields all contain null values. The second statement can immediately set the variable's Name value without needing to use the new keyword to create a new instance of the structure.

Other Differences

Another important difference between value and reference types involves the way the program assigns values to them.

If a program sets one reference variable equal to another, then they both point to the same object. For example, suppose ann and ben are two variables that hold references to Student objects. Then the statement ben = ann makes the variable ben refer to the same object to which ann refers.

Figure 17.1 shows this operation graphically. Initially (the picture on the left) variable ann contains a reference to a Student object and variable ben contains the special value null (represented by the box with an X in it) that means it doesn't refer to anything. After executing the statement ben = ann, both variables contain references to the same Student object (the picture on the right).

Image described by surrounding text.

Figure 17.1

Because the two reference variables refer to the same object, if you use one variable to change the object, the other variable also sees the change. For example, if you execute the statement ben.FirstName = "Ben", then the value ann.FirstName will also contain the valueBen.

In contrast, if you set a variable with a value type equal to another, the first variable receives a copy of the second variable's value. For example, suppose cindy and dan are two variables of the structure type Person. The Person type might be very similar to the Studenttype, except it's a structure (value type) instead of a class (reference type). In that case, the statement dan = cindy makes the variable dan hold a copy of the values in the structure cindy.

Figure 17.2 shows this operation graphically. Initially (the picture on the left) variables cindy and dan each contain Person data. This time the variables include all of the data inside the rectangles; they're not just references pointing to values stored someplace else. After executing the statement dan = cindy, both variables contain separate copies of the same data.

Image described by surrounding text.

Figure 17.2

Because the two value variables refer to different copies of the same data, changing one doesn't change the other. For example, if you execute the statement dan.Name = "Dan", then the value cindy.Name will still be Cindy.

The Structure Versus Class example program, which is available in this lesson's downloads, demonstrates this difference. This issue is quite important, so it will be worth your time to download the example and study it until you're sure you understand it.

So which should you use, a structure or a class? In many programs the difference doesn't matter much. As long as you are aware of the relevant differences, you can often use either.

Microsoft's “Classes and Structs (C# Programming Guide)” web page at msdn.microsoft.com/library/ms173109.aspx gives this advice:

In general, classes are used to model more complex behavior, or data that is intended to be modified after a class object is created. Structs are best suited for small data structures that contain primarily data that is not intended to be modified after the struct is created.

If you follow that advice, then a more complex piece of data such as a Person or Student should probably be implemented as a class. You may need to update Person or Student information over time, so that also indicates that these should probably be classes.

In contrast, suppose you're writing an oven control program and you want a data type to store temperature data. In that case, you might store data in a Temperature structure.

On some level, it doesn't make sense for a particular temperature value to change, although it might make sense for an oven's temperature to change. For example, the oven's temperature might start at 75° and warm up to 375°. The temperature 75° hasn't changed; it's the oven's temperature that has changed. Instead of updating the temperature variable, the program would set the variable equal to the new temperature value.

To see the difference, think back to the Student example. If Ann moves, you'll need to change her address (assuming the Student class contains name, address, phone number, and other relevant data). Ann herself hasn't changed, so it doesn't really make sense to set the ann variable equal to a whole new Student object. Instead you can just update the ann.Address value.

If you think I'm just being nit-picky and splitting hairs here, you're right. The difference is there, but for practical purposes it often doesn't make a huge difference whether you use a class or a structure. A lot of C# programmers use classes instead of structures basically all of the time. (Partly I suspect so they don't have to remember the differences between value and reference types.) If you're using classes and structures defined by Microsoft or some other programmer, then the differences matter, but when you're writing your own code, you can pick whichever makes the most sense to you.

Where to Put Structures

You can define structures in a couple places.

First, you can define a structure inside a class but outside of any of its methods. For example, you can define a structure inside a form class. Then the structure is visible only inside the class that contains it. If code outside of the class doesn't need to use the structure, this restricts the structure's visibility so it prevents possible confusion in the outside code.

Second, you can define a structure in the file that defines a class but outside of the class's code. For example, you can put it at the bottom of the class just before the final closing brace that ends the namespace statement started at the top of the file. In that case, the structure is visible to all of the code in the project (assuming you give it enough visibility, for example, public).

The second method can be a bit confusing because the same file defines a class and a structure. A third place you can define a structure for use by the whole program is in its own module. The easiest way to do that is to use the Project menu's Add Class command. Give the class the name you want to give the structure and click Add. After Visual Studio creates the class, change the class keyword to struct.

You can define enumerations in the same locations.

Try It

In this Try It, you use an enumeration and a structure to make the address book shown in Figure 17.3. When the user clicks the Add button, the program saves the entered address values. If the user enters a name and clicks Find, the program retrieves the corresponding address data.

Screenshot of Dictionary Addresses window presenting the filled-up textboxes for name, street, city, state, ZIP, email, phone, and preferred contacts with Add and Find buttons at the bottom.

Figure 17.3

Lesson Requirements

In this lesson, you:

· Create the form shown in Figure 17.3.

· Define the ContactMethod enumeration with values None, Email, Phone, and SnailMail.

· Define an Address structure to hold the entered address information.

· Create a Dictionary<string, Address> field to hold the address data.

· Add code to initially select the ComboBox's None entry when the form loads (just so something is selected).

· Add code to the Add button that creates the new entry in the Dictionary.

· Add code to the Find button that retrieves the appropriate entry from the Dictionary and displays it.

NOTE

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

Step-by-Step

· Create the form shown in Figure 17.3.

1. I'm sure you can do this on your own by now.

· Define the ContactMethod enumeration with values None, Email, Phone, and SnailMail.

1. Use code similar to the following at the form's class level (not inside any event handler):

2. // Define contact methods.

3. private enum ContactMethod

4. {

5. None,

6. Email,

7. Phone,

8. SnailMail,

}

· Define an Address structure to hold the entered address information.

1. Use code similar to the following at the form's class level (not inside any event handler):

2. // Define the address structure.

3. private struct Address

4. {

5. public string Name;

6. public string Street;

7. public string City;

8. public string State;

9. public string Zip;

10. public string Email;

11. public string Phone;

12. public ContactMethod PreferredMethod;

}

· Create a Dictionary<string, Address> field to hold the address data.

1. Use code similar to the following at the form's class level (not inside any event handler):

2. // Make a Dictionary to hold addresses.

3. private Dictionary<string, Address> Addresses =

new Dictionary<string, Address>();

· Add code to initially select the ComboBox's None entry when the form loads.

1. Use code similar to the following:

2. // Make sure the ComboBox starts with an item selected.

3. private void Form1_Load(object sender, EventArgs e)

4. {

5. preferredMethodComboBox.SelectedIndex = 0;

}

· Add code to the Add button that creates the new entry in the Dictionary.

1. Use code similar to the following. (Using the indexed syntax instead of the Dictionary's Add method lets the Add button add or update a record.) Optionally you can clear the TextBoxes to get ready for the next address.

2. // Add a new address.

3. private void addButton_Click(object sender, EventArgs e)

4. {

5. // Fill in a new Address structure.

6. Address newAddress;

7. newAddress.Name = nameTextBox.Text;

8. newAddress.Street = streetTextBox.Text;

9. newAddress.City = cityTextBox.Text;

10. newAddress.State = stateTextBox.Text;

11. newAddress.Zip = zipTextBox.Text;

12. newAddress.Email = emailTextBox.Text;

13. newAddress.Phone = phoneTextBox.Text;

14. newAddress.PreferredMethod =

15. (ContactMethod)preferredMethodComboBox.SelectedIndex;

16. // Add the name and address to the dictionary.

17. Addresses[nameTextBox.Text] = newAddress;

18. // Get ready for the next one.

19. nameTextBox.Clear();

20. streetTextBox.Clear();

21. cityTextBox.Clear();

22. stateTextBox.Clear();

23. zipTextBox.Clear();

24. emailTextBox.Clear();

25. phoneTextBox.Clear();

26. preferredMethodComboBox.SelectedIndex = 0;

27. nameTextBox.Focus();

}

· Add code to the Find button that retrieves the appropriate entry from the Dictionary and displays it.

1. Use code similar to the following:

2. // Look up an address.

3. private void findButton_Click(object sender, EventArgs e)

4. {

5. // Get the Address.

6. Address selectedAddress = Addresses[nameTextBox.Text];

7. // Display the Address's values.

8. nameTextBox.Text = selectedAddress.Name;

9. streetTextBox.Text = selectedAddress.Street;

10. cityTextBox.Text = selectedAddress.City;

11. stateTextBox.Text = selectedAddress.State;

12. zipTextBox.Text = selectedAddress.Zip;

13. emailTextBox.Text = selectedAddress.Email;

14. phoneTextBox.Text = selectedAddress.Phone;

15. preferredMethodComboBox.SelectedIndex =

16. (int)selectedAddress.PreferredMethod;

}

Exercises

1. Copy the program you built for this lesson's Try It. Add a Delete button that removes an item by calling the Dictionary's Remove method.

2. In addition to simple fields, structures can contain arrays. Copy the program you built for Exercise 1 and modify the Address structure so it contains an array holding three phone numbers: home, work, and cell. (Hint: Before you can store values in the array, you need to allocate it as in theAddress.Phones = new string[3].)

3. Exercise 2 uses a structure that contains an array. You can also make an array that contains structures.

Make a program that creates an array holding five Address structures of the kind used by the program you wrote for Exercise 2. When the program starts, initialize the array to literal values hardcoded into the program. Place the structures' names in a ComboBox. When the user selects an entry from the ComboBox, display the corresponding data.

4. [Games] Make a program that defines an enumeration to represent the pieces on a chess board. Then display each enumeration value as both a string and an int in a TextBox.

5. [Games] Make a program that uses the enumeration you defined for Exercise 4 to create an array that represents a complete board position. When the program loads, initialize the array to represent a new game.

6. [Games] Make a program that defines a structure to represent a chess move. (Hint: Don't record information that you can deduce from the current board position. For example, if a move represents a capture, you don't need to record that fact because you can figure it out.)

7. If you like, you can give multiple enumeration names the same numeric value by setting them equal to that value. You can even use an enumeration name to calculate the value of a later name.

Suppose you're opening a coffee shop and you want to have the sizes Grande, Enorme, and Demente. Because some customers will be too grouchy to use the fancy names (because they haven't had their coffee yet), those names should be equivalent to the more pedestrian names Big, Huge, and Ginormous. Make a program that creates an enumeration that defines all of those values. Then display each value as both a string and an int in a TextBox.

8. [Games] Suppose you're building a steampunk Wild West fantasy role-playing game. Make a program that defines a structure to represent weapons. It should record the weapon's name, range in feet, and attack value; the number of dice to roll when attacking; and the number of sides on the dice.

9. [Games] To continue building your steampunk Wild West game, make a program that defines a structure to represent a character. It should record the character's name, profession (which can be GunSlinger, Scientist, ConArtist, or Cyborg), primary weapon, and secondary weapon.

10.[Hard] Suppose you're writing a genealogy program. Make a program that defines a structure that can store a person's name and that person's parents (represented by the same structure). When the program starts, initialize a data structure to represent the ancestor tree shown in Figure 17.4.Diagram of a three-generation family tree presenting Me box (bottom) linked to two boxes labeled Dad linked to Grandpa Art and Grandma Ann (left) and Mom linked to Grandpa Bert and Grandma Bea (right).

Figure 17.4

(Hint: Because a structure cannot contain direct instances of itself, you'll need to figure out a way to store the parents in a reference type.)

11.[Hard] Make a program that defines a structure that can store a person's name and that person's children (represented by the same structure). When the program starts, initialize a data structure to represent the descendant tree shown in Figure 17.5.Diagram of a three-generation family tree presenting Me box (top) linked to three boxes labeled Alice, which is linked to Dean, Erma, and Finnigan; Bill; and Carmen, which is linked to Gina and Harry.

Figure 17.5

NOTE

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