#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

#775 – Copying an Array of Anonymously-Typed Objects

You can create an implicitly-typed array that contains an array of anonymously-typed objects.

            var movies = new[] {
                new { Title = "North By Northwest", Year = 1959, Director = "Alfred Hitchcock" },
                new { Title = "Zelig", Year = 1983, Director = "Woody Allen" },
                new { Title = "King Kong", Year = 2005, Director = "Peter Jackson" }};

If you’d like to create a copy of this array, you can use the Clone method.  This will create a new array of the same type and copy all of the anonymously-typed elements to the new array.

            var copy = movies.Clone();

#774 – Passing an Array as an out Parameter

Like other reference types, you can use an array as an output parameter, using the out keyword.  The out keyword indicates that you must assign a value to the array before returning from the method.  Also, within the method, you cannot reference the array parameter before you assign a new value to it.

        static int FindFibsThrough(int numFibs, out int[] fibs)
        {
            // Can't reference element of array
            //int i = fibs[0];

            // Can't assign to element of array
            //fibs[0] = 12;

            if (numFibs < 2)
                throw new Exception("Must return at least 2 numbers in sequence");

            // Must assign new value to array before we return
            fibs = new int[numFibs];

            fibs[0] = 0;
            fibs[1] = 1;
            int sum = 1;

            for (int i = 2; i < numFibs; i++)
            {
                fibs[i] = fibs[i - 1] + fibs[i - 2];
                sum += fibs[i];
            }

            return sum;
        }

You must also use the out keyword when calling the method.

            int[] myFibs;
            int sum = FindFibsThrough(10, out myFibs);

774-001

#773 – Reversing a String that Contains Unicode Characters Expressed as Surrogate Pairs

You can use the Reverse method to reverse the characters in a .NET-based string.  This method works if the string contains Unicode characters that can be expressed as 2-byte UTF16 code points.  This subset of Unicode is known as the Basic Multilingual Plane (BMP) and is able to represent 65,536 unique code points.

UTF16 can represent Unicode code points outside the BMP through the use of surrogate pairs.  Within a series of 16-bit characters, a 32-bit character can appear, stored as a pair of normal UTF16 words.

In practice, it’s quite rare to encounter Unicode characters outside of the BMP, given that this plane can represent characters from most living languages.

To reverse a string that contains surrogate pairs, you can use the Microsoft.VisualBasic.Strings.StrReverse method.

            // 8 byte string includes surrogate pair
            string s = "A𠈓C";

            // Won't handle surrogate pair
            string s2 = new string(s.Reverse().ToArray());

            // Will handle surrogate pair
            string s3 = Microsoft.VisualBasic.Strings.StrReverse(s);

773-001

#772 – Initializing an Array as Part of a Method Call

When you want to pass an array to a method, you could first declare the array and then pass the array by name to the method.

Suppose that you have a Dog method that looks like this:

        public void DoBarks(string[] barkSounds)
        {
            foreach (string s in barkSounds)
                Console.WriteLine(s);
        }

You can declare the array and pass it to the method:

            Dog d = new Dog();

            // Declare array and then pass
            string[] set1 = { "Woof", "Rowf" };
            d.DoBarks(set1);

Or you can just initialize a new array instance as part of the method call, without first declaring it:

            // Initialize and pass array without declaring
            d.DoBarks(new string[] { "Grr", "Ack-ack" });

You can also initialize a multi-dimensional array as part of a method call:

            // Initialize and pass multi-dimensional array
            d.AddNumbers(new int[,] { { 1, 2, 3 }, { 9, 10, 11 }, { 100, 12, 32 } });

772-001

#771 – Summary of System.Object Members

Every type inherits from System.Object, directly or indirectly, and therefore has access to its members.  Here’s a brief summary of all members of System.Object.

  • Object  constructor –  called when object is created, ultimately called for every object
  • Equals(object) – compares object to specified object; true for reference types if parameter references the current object  [virtual]
  • Equals(object,object) – static method to compare two objects
  • Finalize – called when the object is being destroyed  [virtual]
  • GetHashCode – is meant to provide a unique number used in hashing algorithms, but should be overridden so that value returned is unique based on object contents  [virtual]
  • GetType – returns the Type of the object
  • MemberwiseClone – Makes a copy (shallow) of the object by copying its members
  • ReferenceEquals(object,object) – static method to see if two references refer to the same object
  • ToString – returns a string that describes the object; by default, just returns the type name