Working with Structure Types

Chia sẻ: Nghia Tuan | Ngày: | Loại File: PDF | Số trang:11

lượt xem

Working with Structure Types

Mô tả tài liệu
  Download Vui lòng tải xuống để xem tài liệu đầy đủ

Làm việc với Cơ cấu loại Bạn đã thấy trong Chương 8 rằng các lớp học xác định các loại tài liệu tham khảo được luôn luôn tạo ra trên heap. Trong một số trường hợp, các lớp có thể chứa dữ liệu rất ít mà những phí quản lý trở nên không cân xứng heap

Chủ đề:

Nội dung Text: Working with Structure Types

  1. Working with Structure Types You saw in Chapter 8 that classes define reference types that are always created on the heap. In some cases, the class can contain so little data that the overhead of managing the heap becomes disproportionate. In these cases it is better to define the type as a structure. Because structures are stored on the stack, the memory management overhead is often reduced (as long as the structure is reasonably small). A structure can have its own fields, methods, and constructors just like a class (and unlike an enumeration), but it is a value type and not a reference type. Common Structure Types You may not have realized it, but you have already used structures in previous exercises in this book. In C#, the primitive numeric types int, long, and float, are aliases for the structures System.Int32, System.Int64, and System.Single, respectively. This means that you can actually call methods on variables and literals of these types. For example, all of these structures provide a ToString method that can convert a numeric value to its string representation. The following statements are all legal statements in C#: int i = 99; Console.WriteLine(i.ToString()); Console.WriteLine(55.ToString()); float f = 98.765F; Console.WriteLine(f.ToString()); Console.WriteLine(98.765F.ToString()); You don't see this use of the ToString method very often, because the Console.WriteLine method calls it automatically when it is needed. Use of the static methods exposed by these structures is much more common. For example, in earlier chapters the static Int32.Parse method was used to convert a string to its corresponding integer value: string s = "42"; int i = Int32.Parse(s); NOTE Because int is simply an alias for Int32, you can also use int.Parse. These structures also include some useful static fields. For example, Int32.MaxValue is the maximum value that an int can hold, and Int32.MinValue is the smallest value you can store in an int.
  2. The following table shows the primitive types in C#, their equivalent types in the .NET Framework, and whether each type is a class or structure. Keyword Type equivalent Class or structure bool System.Boolean Structure byte System.Byte Structure decimal System.Decimal Structure Double System.Double Structure Float System.Single Structure Int System.Int32 Structure Long System.Int64 Structure Object System.Object Class Sbyte System.SByte Structure Short System.Int16 Structure String System.String Class Uint System.UInt32 Structure Ulong System.UInt64 Structure Ushort System.UInt16 Structure Declaring Structure Types To declare your own structure value type, you use the struct keyword followed by the name of the type, followed by the body of the structure between opening and closing braces. For example, here is a struct called Time that contains three public int fields called hours, minutes, and seconds: struct Time { public int hours, minutes, seconds; } As with classes, making the fields of a structure public is not advisable in most cases; there is no way to ensure that public fields contain valid values. For example, anyone could set the value of minutes or seconds to a value greater than 60. A better idea is to make the fields private and provide your structure with constructors and methods, as shown in this example: struct Time { public Time(int hh, int mm, int ss) {
  3. hours = hh % 24; minutes = mm % 60; seconds = ss % 60; } public int Hours() { return hours; } ... private int hours, minutes, seconds; } NOTE By default, you cannot use many of the common operators on your own structure types. For example, you cannot use operators such as == and != on your own struct type variables. However, you can explicitly declare and implement operators for your own struct types. The syntax for doing this is covered in Chapter 19. Use structs to implement simple concepts whose main feature is their value. For example, an int is a value type because its main feature is its value. If you have two int variables that contain the same value (such as 42), one is as good as the other. When you copy a value type variable, you get two copies of the value. In contrast, when you copy a reference type variable, you get two references to the same object. In summary, use structs for lightweight concepts where it makes sense to copy the value, and use classes for more heavyweight concepts where it doesn't make sense to copy the value. Understanding Structure and Class Differences A structure and a class are syntactically very similar but there are a few important differences. Let's look at some of these differences. • You can't declare a default constructor (a constructor with no parameters) for a struct. The following example would compile if Time were a class, but because Time is a struct it fails to compile: • struct Time • { • public Time() { ... } // compile time error • ... } The reason you can't declare your own default constructor in a struct is because the compiler always generates one. In a class, the compiler generates the default constructor only if you don't write a constructor yourself.
  4. The compiler-generated default constructor for a structure always sets the fields to 0, false, or null—just like for a class. Therefore, you should ensure that a structure value created by the default constructor behaves logically and makes sense with these default values. If you don't want to use these default values, you can initialize fields to different values by providing a non-default constructor. However, if you don't initialize a field in the constructor, the compiler won't initialize it for you. This means you must explicitly initialize all the fields in all the structure constructors or you'll get a compile-time error. For instance, although the following example would compile and silently initialize seconds to 0 if Time were a class, because Time is a struct, it fails to compile: struct Time { public Time(int hh, int mm) { hours = hh; minutes = mm; } // compile time error: seconds not initialized ... private int hours, minutes, seconds; } • In a class, you can initialize instance fields at their point of declaration. In a struct, you cannot. For instance, the following example would compile if Time were a class, but because Time is a struct, it causes a compile-time error (reinforcing the rule that every structure must initialize all its fields in all its constructors): • struct Time • { • ... • private int hours = 0; // compile time error • private int minutes; • private int seconds; } The following table summarizes the main differences between a structure and a class. Question Struct Class A structure is a value A class is a reference Is this a value type or a reference type? type. type. Structure instances are Class instances are Do instances live on the stack or the called values and live called objects and live heap? on the stack. on the heap.
  5. Question Struct Class Can you declare a default constructor? No Yes If you declare your own constructor, will the compiler still generate the default Yes No constructor? If you don't initialize a field in your own constructor, will the compiler No Yes automatically initialize it for you? Are you allowed to initialize instance No Yes fields at their point of declaration? There are other differences between classes and structures concerning inheritance. For example, a class can inherit from a base class, but a structure cannot. These differences are covered in Chapter 12, “Working with Inheritance.” Now that you know how to declare structures, the next step is to use them to create values. Declaring Structure Variables After you have defined a struct type, you can use it in exactly the same way as any other type. For example, if you have defined the Time struct, you can create variables, fields, and parameters of type Time, as shown in this example: struct Time { ... private int hours, minutes, seconds; } class Example { public void Method(Time parameter) { Time localVariable; ... } private Time currentTime; } Understanding Structure Initialization
  6. The earlier discussion described how the fields in a structure are initialized by using a constructor. However, because structs are value types, you can create struct type variables without calling a constructor, as shown in the following example: Time now; In this example, the variable is created but its fields are left in their uninitialized state. Any attempt to access the values in these fields will result in a compiler error. The following graphic depicts the state of the fields in the now variable: If you call a constructor, the various rules of structure constructors described earlier guarantee that all the fields in the structure will be initialized: Time now = new Time(); This time, the default constructor initializes the fields in the structure as shown in the following graphic: Note that in both cases, the Time variable is created on the stack. If you've written your own structure constructor, you can also use that to initialize a struct variable. As explained earlier in this chapter, a structure constructor must always explicitly initialize all its fields. For example: struct Time { public Time(int hh, int mm) { hours = hh; minutes = mm; seconds = 0; } ... private int hours, minutes, seconds; } The following example initializes now by calling a user-defined constructor:
  7. Time now = new Time(12, 30); The following graphic shows the effect of this example: Copying Structure Variables You're allowed to initialize or assign one struct variable to another struct variable, but only if the struct variable on the right side is completely initialized (that is, if all its fields are initialized). For instance, the following example compiles because now is fully initialized (the graphic shows the results of performing the assignment): Time now = new Time(12, 30); Time copy = now; The following example fails to compile because now is not initialized: Time now; Time copy = now; // compile time error: now unassigned When you copy a struct variable, each field on the left side is copied directly from its corresponding field on the right side. This copying is done as a fast single-block copy that never throws an exception. It is worth emphasizing that this form of assignment copies the entire struct. Compare this behavior to the equivalent action if Time was a class, in which case both variables (now and copy) would reference the same object on the heap. NOTE C++ programmers should note that this copy behavior cannot be changed. It's time to put this knowledge into practice. In the following exercise, you will create and use a struct type to represent a date. Create and use a struct type 1. Open the StructsAndEnums project, located in the \Microsoft Press\Visual CSharp Step by Step\Chapter 9\StructsAndEnums folder in your My Documents folder, if it is not already open. 2. Display the Date.cs source file in the Code and Text Editor window. 3. Add a struct called Date inside the StructsAndEnums namespace.
  8. This structure should contain three private fields: one called year of type int, one called month of type Month (as declared in the previous exercise), and one called day of type int. The Date structure should look exactly as follows: struct Date { private int year; private Month month; private int day; } Now consider the default constructor that the compiler will generate for Date. This constructor will set the year to 0, the month to 0 (the value of January), and the day to 0. The year value of 0 is not valid (there was no year 0), and the day value of 0 is also not valid (each month starts on day 1). One way to fix this problem is to translate the year and day values by implementing the Date structure so that when the year field holds the value Y, this value represents the year Y + 1900, and when the day field holds the value D, this value represents the day D + 1. The default constructor will then set the three fields to values that represent the Date 1 January 1900. 4. Add a public constructor to the Date struct. This constructor should take three parameters: an int called ccyy for the year, a Month called mm for the month, and an int called dd for the day. Use these three parameters to initialize the corresponding fields. A year field of Y represents the year Y + 1900, so you need to initialize the year field to the value ccyy – 1900. A day field of D represents the day D + 1, so you need to initialize the day field to the value dd – 1. The Date structure should now look like this: struct Date { public Date(int ccyy, Month mm, int dd) { this.year = ccyy - 1900; this.month = mm; = dd - 1; } private int year; private Month month; private int day; }
  9. 5. Add a public method called ToString to the Date structure after the constructor. This method takes no arguments and returns a string representation of the date. Remember, the value of the year field represents year + 1900, and the value of the day field represents day + 1. NOTE The ToString method is a little different from the methods you have seen so far. Every type, including structs and classes that you define, automatically has a ToString method whether you want it or not. Its default behavior is to convert the data in a variable into a string representation of that data. Sometimes the default behavior is meaningful, other times it is less so. For example, the default behavior of the ToString method generated for the Date class simply generates the string “StructsAndEnums.Date”. To quote Zaphod Beeblebrox in The Restaurant at the End of the Universe (Douglas Adams), this is “shrewd, but dull.” You need to define a new version of this method that overrides the default behavior, by using the override keyword. Overriding methods are discussed in more detail in Chapter 12. The ToString method should look as follows: public override string ToString() { return this.month + " " + ( + 1) + " " + (this.year + 1900); } NOTE The + signs in the parentheses are the arithmetic addition operator. The others are the string concatenation operator. Without the parentheses, all occurrences of the + sign will be treated as the string concenation operator because the expression being evaluated is a string. It can be a little confusing when the same symbol in a single expression denotes different operators! 6. Display the Program.cs source file in the Code and Text Editor window. 7. Add a statement to the end of the Entrance method to declare a local variable called defaultDate and initialize it to a Date value constructed by using the defaultDate constructor. Add another statement to Entrance to write defaultDate to the console by calling Console.WriteLine. NOTE The Console.WriteLine method automatically calls the ToString method of its argument to format the argument as a string. The Entrance method should now look like this:
  10. static void Entrance() { ... Date defaultDate = new Date(); Console.WriteLine(defaultDate); } 8. On the Debug menu, click Start Without Debugging to build and run the program. Confirm that January 1 1900 is written to the console (the original output of the Entrance method will be displayed first). 9. Press the Enter key to return to the Visual Studio 2005 programming environment. 10. In the Code and Text Editor window, return to the Entrance method, and add two more statements. The first statement should declare a local variable called halloween and initialize it to October 31 2005. The second statement should write the value of halloween to the console. The Entrance method should now look like this: static void Entrance() { ... Date halloween = new Date(2005, Month.October, 31); Console.WriteLine(halloween); } NOTE When you type the new keyword, Intellisense will automatically detect that there are two constructors available for the Date type. 11. On the Debug menu, click Start Without Debugging. Confirm that October 31 2005 is written to the console after the previous information. 12. Press Enter to close the program. You have successfully used the enum and struct keywords to declare your own value types and then used these types in code. • If you want to continue to the next chapter Keep Visual Studio 2005 running and turn to Chapter 10. • If you want to exit Visual Studio 2005 now On the File menu, click Exit. If you see a Save dialog box, click Yes.
Đồng bộ tài khoản