Programming C#, 2nd Edition
Chapter 7. Structs
A struct is a simple user-defined type, a lightweight alternative to classes. Structs are similar
to classes in that they may contain constructors, properties, methods, fields, operators, nested
types and indexers (see Chapter 9).
There are also significant differences between classes and structs. For instance, structs don't
support inheritance or destructors. More important, although a class is a reference type,
a struct is a value type. (See Chapter 3 for more information about classes and types.) Thus,
structs are useful for representing objects that do not require reference semantics.
The consensus view is that you ought to use structs only for types that are small, simple, and
similar in their behavior and characteristics to built-in types.
Structs are somewhat more efficient in their use of memory in arrays (see Chapter 9).
However, they can be less efficient when used in collections. Collections expect references,
and structs must be boxed. There is overhead in boxing and unboxing, and classes might be
more efficient in large collections.
In this chapter, you will learn how to define and work with structs and how to use
constructors to initialize their values.
7.1 Defining Structs
The syntax for declaring a struct is almost identical to that for a class:
[attributes] [access-modifiers] struct identifier [:interface-list]
{ struct-members }
Example 7-1 illustrates the definition of a struct. Location represents a point on a two-
dimensional surface. Notice that the struct Location is declared exactly as a class would be,
except for the use of the keyword struct. Also notice that the Location constructor takes
two integers and assigns their value to the instance members, x and y. The x and y coordinates
of Location are declared as properties.
Example 7-1. Creating a struct
using System;
public struct Location
{
public Location(int xCoordinate, int yCoordinate)
{
xVal = xCoordinate;
yVal = yCoordinate;
}
123
Programming C#, 2nd Edition
public int x
{
get
{
return xVal;
}
set
{
xVal = value;
}
}
public int y
{
get
{
return yVal;
}
set
{
yVal = value;
}
}
public override string ToString( )
{
return (String.Format("{0}, {1}", xVal,yVal));
}
private int xVal;
private int yVal;
}
public class Tester
{
public void myFunc(Location loc)
{
loc.x = 50;
loc.y = 100;
Console.WriteLine("Loc1 location: {0}", loc);
}
static void Main( )
{
Location loc1 = new Location(200,300);
Console.WriteLine("Loc1 location: {0}", loc1);
Tester t = new Tester( );
t.myFunc(loc1);
Console.WriteLine("Loc1 location: {0}", loc1);
}
}
Output
Loc1 location: 200, 300
In MyFunc loc: 50, 100
Loc1 location: 200, 300
Unlike classes, structs do not support inheritance. They implicitly derive from object (as do
all types in C#, including the built-in types) but cannot inherit from any other class or struct.
Structs are also implicitly sealed (that is, no class or struct can derive from a struct). Like
124
Programming C#, 2nd Edition
classes, however, structs can implement multiple interfaces. Additional differences include
the following:
No destructor or custom default constructor
Structs cannot have destructors, nor can they have a custom parameterless (default)
constructor. If you do not supply a constructor, your struct will in effect be provided
with a default constructor that will zero all the data members or set them to default
values appropriate to their type (see Table 4-2). If you supply any constructor, you
must initialize all the fields in the struct.
No initialization
You cannot initialize an instance field in a struct. Thus, it is illegal to write:
private int xVal = 50;
private int yVal = 100;
though that would have been fine had this been a class.
Structs are designed to be simple and lightweight. While private member data promotes data
hiding and encapsulation, some programmers feel it is overkill for structs. They make the
member data public, thus simplifying the implementation of the struct. Other programmers
feel that properties provide a clean and simple interface, and that good programming practice
demands data hiding even with simple lightweight objects. Whichever you choose is a matter
of design philosophy; the language supports either approach.
7.2 Creating Structs
Create an instance of a struct by using the new keyword in an assignment statement, just as
you would for a class. In Example 7-1, the Tester class creates an instance of Location as
follows:
Location loc1 = new Location(200,300);
Here the new instance is named loc1 and is passed two values, 200 and 300.
7.2.1 Structs as Value Types
The definition of the Tester class in Example 7-1 includes a Location object (loc1) created
with the values 200 and 300. This line of code calls the Location constructor:
Location loc1 = new Location(200,300);
Then WriteLine( ) is called:
Console.WriteLine("Loc1 location: {0}", loc1);
WriteLine( ) is expecting an object, but, of course, Location is a struct (a value type). The
compiler automatically boxes the struct (as it would any value type), and it is the boxed object
that is passed to WriteLine( ). ToString( ) is called on the boxed object, and because the
125
Programming C#, 2nd Edition
struct (implicitly) inherits from object, it is able to respond polymorphically, overriding the
method just as any other object might:
Loc1 location: 200, 300
Structs are value objects, however, and when passed to a function, they are passed by value --
as seen in the next line of code -- in which the loc1 object is passed to the myFunc( )
method:
t.myFunc(loc1);
In myFunc new values are assigned to x and y, and these new values are printed out:
Loc1 location: 50, 100
When you return to the calling function (Main( )) and call WriteLine( ) again, the values
are unchanged:
Loc1 location: 200, 300
The struct was passed as a value object, and a copy was made in myFunc. Try this experiment:
change the declaration to class:
public class Location
and run the test again. Here is the output:
Loc1 location: 200, 300
In MyFunc loc: 50, 100
Loc1 location: 50, 100
This time the Location object has reference semantics. Thus, when the values are changed in
myFunc( ), they are changed on the actual object back in Main( ).
7.2.2 Calling the Default Constructor
As mentioned earlier, if you do not create a constructor, an implicit default constructor is
called by the compiler. We can see this if we comment out the constructor:
/* public Location(int xCoordinate, int yCoordinate)
{
xVal = xCoordinate;
yVal = yCoordinate;
}
*/
and replace the first line in Main( ) with one that creates an instance of Location without
passing values:
// Location loc1 = new Location(200,300);
Location loc1 = new Location( );
126
Programming C#, 2nd Edition
Because there is now no constructor at all, the implicit default constructor is called. The
output looks like this:
Loc1 location: 0, 0
In MyFunc loc: 50, 100
Loc1 location: 0, 0
The default constructor has initialized the member variables to zero.
C++ programmers take note: in C#, the new keyword does not always
create objects on the heap. Classes are created on the heap, and structs
are created on the stack. Also, when new is omitted (as you will see in
the next section), a constructor is never called. Because C# requires
definite assignment, you must explicitly initialize all the member
variables before using the struct.
7.2.3 Creating Structs Without new
Because loc1 is a struct (not a class), it is created on the stack. Thus, in Example 7-1, when
the new operator is called:
Location loc1 = new Location(200,300);
the resulting Location object is created on the stack.
The new operator calls the Location constructor. However, unlike with a class, it is possible
to create a struct without using new at all. This is consistent with how built-in type variables
(such as int) are defined, and is illustrated in Example 7-2.
A caveat: I am demonstrating how to create a struct without using new
because it differentiates C# from C++, and also differentiates how C#
treats classes versus structs. That said, however, creating structs without
the keyword new brings little advantage and can create programs that
are harder to understand, more error prone, and more difficult to
maintain! Proceed at your own risk.
Example 7-2. Creating a struct without using new
using System;
public struct Location
{
public Location(int xCoordinate, int yCoordinate)
{
xVal = xCoordinate;
yVal = yCoordinate;
}
127
Programming C#, 2nd Edition
public int x
{
get
{
return xVal;
}
set
{
xVal = value;
}
}
public int y
{
get
{
return yVal;
}
set
{
yVal = value;
}
}
public override string ToString( )
{
return (String.Format("{0}, {1}", xVal,yVal));
}
public int xVal;
public int yVal;
}
public class Tester
{
static void Main( )
{
Location loc1; // no call to the constructor
loc1.xVal = 75; // initialize the members
loc1.yVal = 225;
Console.WriteLine(loc1);
}
}
In Example 7-2 you initialize the local variables directly, before calling a method of loc1 and
before passing the object to WriteLine( ):
loc1.xVal = 75;
loc1.yVal = 225;
If you were to comment out one of the assignments and recompile:
static void Main( )
{
Location loc1;
loc1.xVal = 75;
// loc1.yVal = 225;
Console.WriteLine(loc1);
}
128
Programming C#, 2nd Edition
you would get a compiler error:
Use of unassigned local variable 'loc1'
Once you assign all the values, you can access the values through the properties x and y:
static void Main( )
{
Location loc1;
loc1.xVal = 75; // assign member variable
loc1.yVal = 225; // assign member variable
loc1.x = 300; // use property
loc1.y = 400; // use property
Console.WriteLine(loc1);
}
Be careful about using properties. Although these allow you to support encapsulation by
making the actual values private, the properties themselves are actually member methods, and
you cannot call a member method until you initialize all the member variables.
129
Programming C#, 2nd Edition
Chapter 8. Interfaces
An interface is a contract that guarantees to a client how a class or struct will behave. When
a class implements an interface, it tells any potential client "I guarantee I'll support
the methods, properties, events, and indexers of the named interface." (See Chapter 4 for
information about methods and properties; see Chapter 12 for info about events, and see
Chapter 9 for coverage of indexers.)
An interface offers an alternative to an abstract class for creating contracts among classes and
their clients. These contracts are made manifest using the interface keyword, which
declares a reference type that encapsulates the contract.
Syntactically, an interface is like a class that has only abstract methods. An abstract class
serves as the base class for a family of derived classes, while interfaces are meant to be mixed
in with other inheritance trees.
When a class implements an interface, it must implement all the methods of that interface; in
effect the class says "I agree to fulfill the contract defined by this interface."
Inheriting from an abstract class implements the is-a relationship, introduced in Chapter 5.
Implementing an interface defines a different relationship that we've not seen until now: the
implements relationship. These two relationships are subtly different. A car is a vehicle, but it
might implement the CanBeBoughtWithABigLoan capability (as can a house, for example).
Mix Ins
In Somerville, Massachusetts, there was, at one time, an ice cream parlor where you
could have candies and other goodies "mixed in" with your chosen ice cream flavor.
This seemed like a good metaphor to some of the object-oriented pioneers from
nearby MIT who were working on the fortuitously named SCOOPS programming
language. They appropriated the term "mix in" for classes that mixed in additional
capabilities. These mix-in or capability classes served much the same role as
interfaces do in C#.
In this chapter, you will learn how to create, implement, and use interfaces. You'll learn how
to implement multiple interfaces and how to combine and extend interfaces, as well as how to
test whether a class has implemented an interface.
8.1 Implementing an Interface
The syntax for defining an interface is as follows:
[attributes ] [access-modifier ] interface interface-name [: base-list ]
{interface-body }
Don't worry about attributes for now; they're covered in Chapter 18.
Access modifiers, including public, private, protected, internal, and protected
internal, are discussed in Chapter 4.
130