Saturday, September 30, 2017

Functional adventures in .NET C# - Part 1, immutable objects

Lately I've been converted to the immutable data train, working with web technologies like React/Redux. I've started to wonder why I don't build all applications with immutable data, as it removes a big headache, if you get a reference to a data structure it will _always_ be the same data in it. Not like when you are working with ordinary data structures, where other threads or sub-functions can modify the in data when you aren't looking.

This is part 1 of this series. Here are links to all the other posts:
Functional adventures in .NET C# - Part 1, Immutable Objects
Functional adventures in .NET C# - Part 2, Application State

So, my native platform is .NET and C#, is there a way to work with immutable data out of the box or do we have to write something ourselves? It turns out that Microsoft has released an immutable library System.Collections.Immutable outside of the ordinary .NET versions as a NuGet package.
It has all the collections like
  • System.Collections.Immutable.ImmutableArray
  • System.Collections.Immutable.ImmutableArray<T>
  • System.Collections.Immutable.ImmutableDictionary
  • System.Collections.Immutable.ImmutableDictionary<TKey,TValue>
  • System.Collections.Immutable.ImmutableHashSet
  • System.Collections.Immutable.ImmutableHashSet<T>
  • System.Collections.Immutable.ImmutableList
  • System.Collections.Immutable.ImmutableList<T>
  • System.Collections.Immutable.ImmutableQueue
  • System.Collections.Immutable.ImmutableQueue<T>
  • System.Collections.Immutable.ImmutableSortedDictionary
  • System.Collections.Immutable.ImmutableSortedDictionary<TKey,TValue>
  • System.Collections.Immutable.ImmutableSortedSet
  • System.Collections.Immutable.ImmutableSortedSet<T>
  • System.Collections.Immutable.ImmutableStack
  • System.Collections.Immutable.ImmutableStack<T>
All well and good! But what of the all the POCO (plain old class object) and domain objects in your code? These need to be converted to immutables as well if you are to build something that can be relied upon.

So, how to do this in a simple way? After searching a bit on how other people are doing this I found this article by @davepermen and decided to take some of the ideas from there.

So here is the pattern that I'm currently using in a space game that I'm writing on my spare time.
public class Fleet
    public static Fleet Default { get; } = new Fleet();

    public uint Id { get; }
    public string Name { get; }
    public ImmutableDictionary<ShipType, ImmutableList<Ship>> Ships { get; }
    private Fleet()
        Ships = ImmutableDictionary<ShipType, ImmutableList<Ship>>.Empty;
    private Fleet(uint id, string name, ImmutableDictionary<ShipType, ImmutableList<Ship>> ships)
        Id = id;
        Name = name;
        Ships = ships;

    public Fleet Modify(uint? id = null,
        string name = null, 
        ImmutableDictionary<ShipType, ImmutableList<Ship>> ships = null)
        return new Fleet
            id ?? Id,
            name ?? Name,
            ships ?? Ships

Some key points.
Private constructor
The constructors are private, as the class is immutable, there is no real reason to have multiple 'new' fleet objects, just get the reference to the Default object.
So to create a new fleet we would do something like:
var fleet = Fleet.Default.Modify(_currentFleetId++, a.Name);
I.e. we get the reference to the default object and call Modify on it with the stuff that we want to have in our new fleet object.

All public fields are getters only
So that you can read the values from outside, but not set them other then from the constructor.

The Modify(T name = null) method
The only way to 'modify' state is to create use this method, and it does not really modify the state but it creates a new state based on the input. All attributes are nullable and with default values so that you only need to specify the values that change. The non-provided values will be copied from this object.
I.e. setting only the name would be like this:
fleet = fleet.Modify(name: "Battlestar!");

Explicit set to null methods
If you need to be able to set a value specifically to null, you would have to create a specific Modify method for that value, as if you send in null to the one provided it would copy the value from this instead of setting it to null.

public Ship RemoveTargetCoordinates()
    return new Ship
In this example we provide a copy from the current instance of all values but the one we want to set to null. Not the prettiest solution but works for now.

Update 20171003
There is no need to set to null, set the value to Default instead and instead of doing a null check check against the Default value. This solved all my 'set to null' issues in my code base so far. And this way there will not be a null reference exception in the middle of a chained function call.

Hope this helps someone out there getting started with functional programming in C#

No comments:

Post a Comment