Wednesday, 21 December 2011

Lesson 12: Structs

This lesson will teach you about the C# struct. Our objectives are as follows:
  • Understand the Purpose of structs.
  • Implement a struct.
  • Use a struct.

What is a struct?

A struct is a value type. To help understand the struct, it's helpful to make a comparison with classes, as described in Lesson 7: Introduction to Classes and subsequent chapters. While a struct is a value type, a class is a reference type. Value types hold their value in memory where they are declared, but reference types hold a reference to an object in memory. If you copy a struct, C# creates a new copy of the object and assigns the copy of the object to a separate struct instance. However, if you copy a class, C# creates a new copy of the reference to the object and assigns the copy of the reference to the separate class instance. Structs can't have destructors, but classes can have destructors. Another difference between a struct and class is that a struct can't have implementation inheritance, but a class can, as described in Lesson 8: Class Inheritance. Although a struct can't have implementation inheritance, it can have interface inheritance, as described in Lesson 13: Interfaces, which is the next lesson following this one. Lesson 22: Topics on C# Type, digs deeper into the differences between value and reference types, providing code that demonstrates the concepts that are introduced here.
The .NET Framework includes many types that are structs, including many of the built-in types. For example, a System.Int32 is a C# int, a System.Single is a C# float, and a System.Bool is a C# bool. The C# built-in types are aliases for .NET Framework types, giving you language-specific syntax. If you look at the documentation for any of these .NET Framework types, you'll see them declared as struct types. That means you'll need to recognize what a struct type is when you see it, which the next section helps with by showing you how to create your own custom struct type.

Creating a Custom struct Type

While the behavior of class and struct types are very different, their syntax is similar. You declare the type and its members with the primary visual difference being that a struct uses the keyword struct and a class uses the keyword class. The example in Listing 12-1 demonstrates how to define a custom struct. In this case, the struct is a Rectangle with Width and Height properties, similar to what you might use to represent a rectangular shape on a screen.
Listing 12-1. Defining a struct
/// <summary>
/// Custom struct type, representing
    a rectangular shape
/// </summary>
struct Rectangle
{
    /// <summary>
    /// Backing Store for Width
    /// </summary>
    private int m_width;

    /// <summary>
    /// Width of rectangle
    /// </summary>
    public int Width 
    {
        get
        {
            return m_width;
        }
        set
        {
            m_width = value;
        }
    }

    /// <summary>
    /// Backing store for Height
    /// </summary>
    private int m_height;

    /// <summary>
    /// Height of rectangle
    /// </summary>
    public int Height
    {
        get
        {
            return m_height;
        }
        set
        {
            m_height = value;
        }
    }
}
As you can see, the Rectangle struct in Listing 12-1 looks very much like a class with a couple properties, except that it uses the keyword struct, instead of the keyword class, to declare that Rectangle is a struct.

Using a struct

To use a struct, instantiate the struct and use it just like a class. Listing 12-2 shows how to instantiate the Rectangle struct and access its properties.
Listing 12-2. Using a Struct
using System;

/// <summary>
/// Example of declaring and using
    a struct
/// </summary>
class StructExample
{
    /// <summary>
    /// Entry point: execution starts
        here
    /// </summary>
    static void Main()
    {
        // instantiate a new Rectangle struct
        // where Width is set to 1 and Height
            is set to 3
 Rectangle rect1 = new Rectangle();
        rect1.Width = 1;
        rect1.Height = 3;

        // show the value of Width and Height
            for rect1
        Console.WriteLine("rect1: {0}:{1}", rect1.Width, rect1.Height);

        Console.ReadKey();
    }
}
The code in the Main method of Listing 12-2 instantiates a new Rectangle struct and sets its Height and Width properties. The experience is similar to how a class can be used. Here's the output:
rect1: 1:3
An alternate way of instantiating a struct and setting its properties is with an object initializer, shown below:
        // you can also use object
    initialization syntax
        Rectangle rect11 = new Rectangle
        {
            Width = 1,
            Height = 3
        };
Notice that the object initializer uses curly braces and sets properties via a comma-separated list of name/value pairs.

Overloading struct Constructors

The two previous examples of instantiating a struct, via constructor only and via object initializer, used the default (parameterless) constructor of the struct. The default constructor is implicitly defined by C# and you can't implement the default constructor yourself.  The default constructor initializes all struct fields to default values. i.e. integrals are 0, floating points are 0.0, and booleans are false. If you need custom constructor overloads, you can add new constructors, as long as they have one or more parameters. Listing 12-3 shows a customization of the Rectangle struct from Listing 12-1 that includes a constructor overload.
Listing 12-3: Overloading a struct Constructor
/// <summary>
/// Custom struct type, representing
    a rectangular shape
/// </summary>
struct Rectangle
{
    /// <summary>
    /// Backing Store for Width
    /// </summary>
    private int m_width;

    /// <summary>
    /// Width of rectangle
    /// </summary>
    public int Width 
    {
        get
        {
            return m_width;
        }
        set
        {
            m_width = value;
        }
    }

    /// <summary>
    /// Backing store for Height
    /// </summary>
    private int m_height;

    /// <summary>
    /// Height of rectangle
    /// </summary>
    public int Height
    {
        get
        {
            return m_height;
        }
        set
        {
            m_height = value;
        }
    }

 /// <summary> /// Instantiate rectangle struct with
    dimensions /// </summary>
    /// <param name="width">Width
        to make new rectangle</param> 
            /// <param name="height">Height to make new rectangle</param>
    public Rectangle(int width, 
            int height) { m_width = width; m_height = height; }
}
The highlighted portion of code in Listing 12-3 is a constructor overload. Constructors are named the same as their containing struct, which is Rectangle in this case. This Rectangle constructor overload has two parameters, which it assigns to backing stores that are encapsulated by properties for calling code. Listing 12-4 shows an example of how you would use the constructor overload from Listing 12-3 to instantiate a new Rectangle.
Listing 12-4: Instantiating a struct Through a Constructor Overload
using System;

/// <summary>
/// Example of declaring and using
    a struct
/// </summary>
class StructExample
{
    /// <summary>
    /// Entry point: execution starts
        here
    /// </summary>
    static void Main()
    {
        // instantiate a new Rectangle struct
        // where Width is set to 5 and Height
            is set to 7
 Rectangle rect2 = new Rectangle(5, 7);

        // show the value of Width and Height
            for rect2
        Console.WriteLine("rect2: {0}:{1}", rect2.Width, rect2.Height);

        Console.ReadKey();
    }
}
The code in the Main method of Listing 12-4 instantiates a Rectangle struct and displays the values set via the constructor overload. When instantiating rect2, the code passes the values 5 and 7 as arguments. From the constructor in Listing 12-3, you can see that the Width of rect2 will be set to 5 and the Height of rect2 will be set to 7. Here's the output from Listing 12-4:
rect2: 5:7

Adding a Method to a struct

All of the examples so far showed how you can add properties and constructors to a struct, but you can also add methods to a struct. Defining a method in a struct is the same as defining a method in a class. Listing 12-5 shows the Rectangle struct with a method named Add.
Listing 12-5: Adding a Method to a struct
/// <summary>
/// Custom struct type, representing
    a rectangular shape
/// </summary>
struct Rectangle
{
    /// <summary>
    /// Backing Store for Width
    /// </summary>
    private int m_width;

    /// <summary>
    /// Width of rectangle
    /// </summary>
    public int Width 
    {
        get
        {
            return m_width;
        }
        set
        {
            m_width = value;
        }
    }

    /// <summary>
    /// Backing store for Height
    /// </summary>
    private int m_height;

    /// <summary>
    /// Height of rectangle
    /// </summary>
    public int Height
    {
        get
        {
            return m_height;
        }
        set
        {
            m_height = value;
        }
    }

    /// <summary>
    /// Instantiate rectangle struct
        with dimensions
    /// </summary>
    /// <param name="width">Width
        to make new rectangle</param>
    /// <param name="height">Height
        to make new rectangle</param>
    public Rectangle(int width, int height)
    {
        m_width = width;
        m_height = height;
    }


    /// <summary> 
    /// Increase the size of this rectangle by the size of the specified rectangle
    /// </summary>
    /// <param name="rect">Rectangle that will be added to this rectangle</param>
    /// <returns>New rectangle created by adding rect to this rectangle</returns>
    public Rectangle Add(Rectangle rect)
    { 
        // create instance of rectangle struct with default constructor
        Rectangle newRect = new Rectangle();

        // add matching axes and assign to new Rectangle struct
        newRect.Width = Width + rect.Width; newRect.Height = Height + rect.Height;

        // return new Rectangle struct
        return newRect; 
    }
}
The highlighted code in Listing 12-5 is a method named Add. It might or might not make sense to add two Rectangle structs together, but the example demonstrates how to define a method in a struct. In this case, the Add method will increase the Height and Width of the current Rectangle instance by adding the Height and Width in the rect parameter. The result of the method is a new Rectangle with the added properties.

Calling a struct Method

You can call the Add method, from Listing 12-5, through an instance of a Rectangle struct. Listing 12-6 shows how to instantiate two Rectangle structs, call the Add method and assign the result of the Add method call to another Rectangle struct.
Listing 12-6: Calling a struct Method
using System;

/// <summary>
/// Example of declaring and using
    a struct
/// </summary>
class StructExample
{
    /// <summary>
    /// Entry point: execution starts
        here
    /// </summary>
    static void Main()
    {
        // instantiate a new Rectangle struct
        // where Width is set to 1 and Height is set to 3
 Rectangle rect1 = new Rectangle();
        rect1.Width = 1;
        rect1.Height = 3;

        // show the value of Width and Height for rect1
        Console.WriteLine("rect1: {0}:{1}", rect1.Width, rect1.Height);

        // instantiate a new Rectangle struct
        // where Width is set to 5 and Height is set to 7
 Rectangle rect2 = new Rectangle(5, 7);

        // show the value of Width and Height for rect2
        Console.WriteLine("rect2: {0}:{1}", rect2.Width, rect2.Height);


        // invoke the Add method on the rect1 Rectangle struct instance,
        // passing the rect2 Rectangle struct instance as an argument
        // and assigning the new copy of the value returned by the
        // Add method to the rect3 Rectangle struct.
        Rectangle rect3 = rect1.Add(rect2);

        // show the value of Width and Height for rect3
        Console.WriteLine("rect3: {0}:{1}", rect3.Width, rect3.Height);

        Console.ReadKey();
   }
}
In the Main method of Listing 12-6, the code instantiates rect1 and rect2, which are both Rectangle structs, assigning values to their Height and Width properties. The struct instantiation examples should be familiar by now because they are the same as earlier examples. What's useful about Listing 12-6 is the highlighted code, which shows how to invoke the Add method of the Rectangle struct. The code invokes the Add method of the rect1 instance and passes rect2 as the Rectangle struct to be added to rect1. The Add method in Listing 12-5 shows what happens when this code executes. In Listing 12-6, the return value of the Add method is assigned to rect3, which is a larger Rectangle with each of its sides equal to the sum of the individual sides of rect1 and rect2. Here's the output:
rect1: 1:3 
rect2: 5:7 
rect3: 6:10

Summary

This lesson described what a struct was and identified a few differences between class and struct types. You learned how to create a struct. You can instantiate a struct either via a default constructor or a custom constructor overload that you write. You also saw how to implement properties and methods in structs.

No comments:

Post a Comment