ps

If we follow the functional paradigm, we should refrain from state mutation altogether: once created, an object never changes. This is the subject of an entire chapter of the C# FP Orange Book

Here I give an overview of Value Types, Reference types and immutability.

Benefits of avoiding mutation

The options discussed in the book are:

  • Immutability by convention (but mutation can creep in!)
  • Define immutable objects in C# (required some extra work in defining constructors)
  • Write data objects in F#

1. Value Types

Variables of value types directly contain the data:

  • simple types: bool, byte, char, decimal, int etc…
  • structs (eg Point), enum
// Value types
// the value of 42 is copied to to the variable i
int i = 42;
Update(i);
void Update(int j)
{
    WriteLine(j); // 42
    j = 43;
    WriteLine(j); // 43
}
WriteLine(i); // 42

Value types are non-nullable by default, so if you want an int to be nullable you have to declare it as int?

2. Reference Types

Variables of reference type store references to their data eg:

  • class
  • object
  • string
public class Thing
{
    public string Name { get; set; }
}

var q = new Thing { Name = "test1" };
UpdateThing(q);
// A function with side effects
void UpdateThing(Thing r)
{
    // q and r reference the same object
    r.Name = "test2";
}
WriteLine(q.Name); // test2

Null

Reference types are nullable ie

Thing u = null;

if (u.Name == "Dave") return;

This compiles but gives us the System.NullReferenceException at runtime.

In C#8 we got Nullable and non-nullable reference types and can turn this on by setting Nullable

<PropertyGroup>
  <OutputType>Exe</OutputType>
  <Nullable>enable</Nullable>
</PropertyGroup>

This means we get compiler warnings now. I use this as standard in all my projects.

3. String - Reference type but behaves like a Value Type

var aa = "test1";
UpdateString(aa);
void UpdateString(string bb) => bb = "test2";
WriteLine(aa); // test1

Strings are not value types since they can be huge.

Value types are stored on the stack which is 1MB for 32-bit and 4MB for 64-bit, so you’d incur many penalties for doing this.

Reference types are stored on the heap.

It matters whether an object is a value or a reference type but the rest is an implementation detail

String vs string - string is an alias for System.String and can be used interchangeably. R# suggests to use the alias string.

4. Immutable Data Object - Reference type

To enforce immutability on our Data Object / Custom Type / Anemic Object, which is a reference type, we can make the public property read only ie no set.

public class Person
{
    public string Name { get; } // C#6 getter-only auto-property
    public int Age { get; }

    public Person(string name, int age)
    {
        Name = name;
        Age = age;
    }
    public override string ToString() => $"{Name} : {Age}";
}

// Immutable data object (so can only set public property on construction)
var s = new Person("test1", 45);
var u = UpdatePersonName(s);
// Make a copy of the Person object and return with an updated value
Person UpdatePersonName(Person t) => new Person("test2", t.Age);
WriteLine(s); // test1, 45
WriteLine(u); // test2, 45

This has been a brief look at Value types (eg int), Reference types (eg custom classes) and making Immutable Objects.

Have a look at my series on leaning FP in C#