[ Team LiB ] Previous Section Next Section

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 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 changing 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( );

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 int xVal;
    public int yVal;

    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 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.

    [ Team LiB ] Previous Section Next Section