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.
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)
{
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.
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
Is this a value type or a reference type?
A structure is a value
type.
A class is a reference
type.
Do instances live on the stack or the
heap?
Structure instances are
called values and live
on the stack.
Class instances are
called objects and live
on the heap.
Question Struct Class
Can you declare a default constructor? No Yes
If you declare your own constructor, will
the compiler still generate the default
constructor?
Yes No
If you don't initialize a field in your own
constructor, will the compiler
automatically initialize it for you?
No Yes
Are you allowed to initialize instance
fields at their point of declaration?
No Yes
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