#782 – You Can Create an Instance of a struct Without the new Keyword

Like a class, you can create an instance of a struct using the new keyword.  You can either invoke the default parameterless constructor, which initializes all fields in the struct to their default values, or you can invoke a custom constructor.

            // Method 1 - Parameterless constructor, data in struct initialized to default values
            DogCollar collar = new DogCollar();

            // Method 2 - Call custom constructor
            DogCollar collar2 = new DogCollar(10.0, 0.5);

In either case, a constructor is called.

You can also create an instance of a struct by just declaring it, without using the new operator.  The object is created, although you can’t use it until you’ve explicitly initialized all of its data members.

            // Method 3 - Just declare it
            DogCollar collar3;

            // Error - Can't use collar3 yet (use of unassigned field)
            Console.WriteLine(collar3.Length);

We need to first initialize all data members:

            // Correct - initialize first
            DogCollar collar3;
            collar3.Length = 10.0;
            collar3.Width = 5.0;
            Console.WriteLine(collar3.Length);
Advertisement

#781 – A struct Can Implement an Interface

Like a class, a struct can implement an interface.  In the example below, the DogCollar struct implements the IStrapDimensions interface, which contains a couple of properties and a method.

    public interface IStrapDimensions
    {
        double Length { get; }
        double Width { get; }

        double CalcArea();
    }

    public struct DogCollar : IStrapDimensions
    {
        private double length;
        public double Length
        {
            get { return length; }
        }

        private double width;
        public double Width
        {
            get { return width; }
        }

        public double CalcArea()
        {
            return Length * Width;
        }

        public DogCollar(double len, double wid)
        {
            length = len;
            width = wid;
        }
    }

#780 – The Case for Immutable structs

You can run into problems when a struct is mutable.  (E.g. when used as a property, in a collection, or when modifying a struct through a method).

You can avoid problems by being careful about how you use the struct and by being aware of value type semantics (you get a copy of the value-typed object, rather than a reference to it).

You can also avoid problems by making your custom structs immutable.  This means:

  • Exposing the data in the struct exclusively through read-only properties
  • Defining methods that modify the value in the struct to return a new instance of the struct

For an example of this, look at the System.DateTime type, which is a struct.  Its properties all have only a get accessor, so you can’t change them.  And methods that change the value of a DateTime, e.g. AddDays, return a new instance of a DateTime.

#779 – Methods in struct that Modify Elements Can Be Dangerous

If you have a method in a struct that modifies a data member of the struct, you can run into unexpected results, due to the value type semantics of the struct.

Assume that we have a struct that includes a method that can change the value of one of its data members.  The method works just fine for a locally defined instance of the struct.

            // Method that modifies struct works if local
            DogCollarInfo collar = new DogCollarInfo(0.5, 8.0);
            collar.Dump();
            collar.DoubleLength();
            collar.Dump();

779-001
But if you have a property of some class whose type is this struct, it’s no longer safe to call this method. Because the property’s get accessor returns a copy of the struct, the data in the original struct won’t get modified.

            // Does not work if struct is property
            Dog d = new Dog("Kirby");
            d.Collar = new DogCollarInfo(0.5, 8.0);
            d.Collar.Dump();
            d.Collar.DoubleLength();
            d.Collar.Dump();   // Length not doubled!

779-002

#778 – A struct Isn’t Mutable When Used in a Collection

struct is normally mutable, i.e. you can modify the values of its members directly.

However, if a struct is used in a collection class, like a List<T>, you can’t modify its members.  Referencing the item by indexing into the collection returns a copy of the struct, which you can’t modify.  To change an item in the list, you need to create a new instance of the struct.

            List<DogCollarInfo> collarList = new List<DogCollarInfo>();

            // Create a few instances of struct and add to list
            collarList.Add(new DogCollarInfo(0.5, 14.0));
            collarList.Add(new DogCollarInfo(0.3, 12.0));

            // Compile-time error: Can't modify '...' because it's not a variable
            collarList[1].Length = 22.0;

            // Do this instead
            collarList[1] = new DogCollarInfo(collarList[1].Width, 22.0);

If you store the structs in an array, then you can change the value of one of the struct’s members.

            DogCollarInfo[] arr = new DogCollarInfo[2];
            arr[0] = new DogCollarInfo(0.5, 14.0);
            arr[0].Length = 5.0;  // OK

#777 – A struct Isn’t Mutable When Used as a Property

A struct is normally mutable, i.e. you can modify the values of its members directly.  Assume that we have a DogCollarInfo struct with Width and Length members.  We can create the struct and then later modify it.

            // Create struct
            DogCollarInfo collar1 = new DogCollarInfo(0.5, 14.0);
            collar1.Dump();

            // Modify data in struct
            collar1.Length = 20.0;
            collar1.Dump();

777-001

However, if a struct is used as a property in another object, we can’t modify its members.

            // Create Dog object and set Collar property
            Dog d = new Dog("Kirby");
            d.Collar = collar1;

            // Compile-time error: Can't modify Collar because it's not a variable
            d.Collar.Length = 10.0;

Because the struct is a value type, the property accessor (get) returns a copy of the struct.  It wouldn’t make sense to change the copy, so the compiler warns us.

You can instead create a new copy of the struct:

            // Do this instead
            d.Collar = new DogCollarInfo(d.Collar.Width, 10.0);
            Console.WriteLine("Dog's collar:");
            d.Collar.Dump();

777-002

#776 – Declaring and Using Nullable structs

You can make any value type nullable, allowing it to either be null or to contain one of its normal values.

Since user-defined structs are value types, you can also make any struct nullable, using Nullable<T> or T? notation.

            // Regular struct
            GuyInfo gi1 = new GuyInfo("Isaac", 1642);

            // Nullable, with no value
            Nullable<GuyInfo> gi2 = null;

            // Nullable, 2nd form, with value
            GuyInfo? gi3 = new GuyInfo("Albert", 1879);

            bool hasVal1 = gi2.HasValue;
            bool hasVal2 = gi3.HasValue;

776-001

#767 – A struct Is Implicitly Sealed

Every struct in C#, whether it is user-defined or defined in the .NET Framework, is sealed–meaning that you can’t inherit from it.  A struct is sealed because it is a value type and all value types are sealed.

A struct can implement an interface, so it’s possible to see another type name following a colon, after the name of the struct.

In the example below, we get a compile-time error when we try to define a new struct that inherits from the one defined above.

    public struct PersonName
    {
        public PersonName(string first, string last)
        {
            First = first;
            Last = last;
        }

        public string First;
        public string Last;
    }

    // Error at compile time: Type 'PersonName' in interface list is not an interface
    public struct AngryPersonName : PersonName
    {
        public string AngryNickname;
    }

#647 – A struct Can Implement an Interface

You’ll most often see an interface implemented by a class, but a struct can also implement an interface.

struct implements an interface in the same way as a class–by listing the name of the interface after the name of the struct and then by providing implementations of the members in the interface.

Suppose that we had the following interface:

    public interface IBoxCalcs
    {
        double CalcVolume();
        double CalcSurfaceArea();
    }

We can then implement this interface in a struct.

    public struct BoxSize : IBoxCalcs
    {
        public double x;
        public double y;
        public double z;

        // Constructor that fully initializes the object
        public BoxSize(double x, double y, double z)
        {
            this.x = x;
            this.y = y;
            this.z = z;
        }

        public double CalcVolume()
        {
            return x * y * z;
        }

        public double CalcSurfaceArea()
        {
            return (2 * x * y) + (2 * x * z) + (2 * y * z);
        }
    }

#645 – You Can Chain to the Default Constructor for a struct

You can use the this keyword in a constructor within a struct to invoke other constructors (constructor chaining).

public BoxSize(double x, double y)
    : this(x, y, 1.0)
    {
    }

Every struct also has a default parameterless constructor that you can explicitly invoke when creating an object of the struct’s type.

            BoxSize bs = new BoxSize();

Additionally, you can use the this keyword to chain to this default constructor from another constructor.  Doing so results in all fields being assigned to default values.

        // Initialize x and y to specified values,
        //   initialize z to default value.
        public BoxSize(double x, double y)
            : this()
        {
            this.x = x;
            this.y = y;
        }