You 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
.
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);
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 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 then 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( )
.
As mentioned earlier, if you do not create a constructor, an implicit default constructor will be 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( );
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.
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;
}
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); }
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.
3.17.81.201