Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Change property of immutable type

I have stored immutable types in a temporary CQRS read store (query/read side, in fact implemented by a simple List with abstraction access layer, I don't want to use a full blown document database at this point). These read stores contains items like the following:

public class SomeItem
{
    private readonly string name;
    private readonly string description;

    public SomeItem(string name, string description)
    {
        this.name = name;
        this.description = description;
    }

    public string Name
    {
        get { return this.name; }
    }

    public string Description
    {
        get { return this.description; }
    }
}

Now I want to change the Name and in a 2nd Command the Description. These changes should keep the current state, which means for the example above:

// initial state
var someItem = new SomeItem("name", "description");

// update name -> newName
someItem = new SomeItem("newName", someItem.Description);

// update description -> newDescription
someItem = new SomeItem(someItem.Name, "newDescription");

This does look error prone to me if you have several properties... you have to manage keeping the current state. I could add something like Clone() to every type but I think/hope there is something better out there that performs well and is easy to use, I don't want to write much repetive code (lazy programmer). Any suggestions how to improve the code above? The SomeItem class needs to stay immutable (transported through several different threads).

like image 959
Beachwalker Avatar asked Nov 15 '25 02:11

Beachwalker


2 Answers

With C#9 we got the with operator for this purpose.

   public record Car
    {
        public string Brand { get; init; }   
        public string Color { get; init; }    
    }
    var car = new Car{ Brand = "BMW", Color = "Red" }; 
    var anotherCar = car with { Brand = "Tesla"};

With-expressions When working with immutable data, a common pattern is to create new values from existing ones to represent a new state. For instance, if our person were to change their last name we would represent it as a new object that’s a copy of the old one, except with a different last name. This technique is often referred to as non-destructive mutation. Instead of representing the person over time, the record represents the person’s state at a given time. To help with this style of programming, records allow for a new kind of expression; the with-expression:

News in C#9

NOTE With operator is only supported by records.

Records At the core of classic object-oriented programming is the idea that an object has strong identity and encapsulates mutable state that evolves over time. C# has always worked great for that, But sometimes you want pretty much the exact opposite, and here C#’s defaults have tended to get in the way, making things very laborious.

Recods in C#9

like image 61
Esset Avatar answered Nov 17 '25 15:11

Esset


Sadly, there's no simple way in C#. F# has the with keyword, and you could have a look at lenses, but it's all somewhat tedious in C#. The best I can give you is something like this:

class SomeItem
{
  private readonly string name;
  private readonly string description;

  public SomeItem(string name, string description)
  {
    this.name = name;
    this.description = description;
  }

  public SomeItem With
    (
      Option<string> name = null,
      Option<string> description = null
    )
  {
    return new SomeItem
      (
        name.GetValueOrDefault(this.name), 
        description.GetValueOrDefault(this.description)
      );
  }
}

This allows you to do the updates like

var newItem = oldItem.With(name: "My name!");

I've used this approach with extension methods and T4s to great effect, but even when you write the code manually, it's reasonably reliable - if you add a new field, you must add it to the With as well, so it works quite well.

There's a few more approaches if you are willing to tolerate runtime code generation and reducing type safety, but that's kind of going against the grain IMO.

like image 26
Luaan Avatar answered Nov 17 '25 14:11

Luaan