Changing property value without breaking immutability

Viewed 189

I'm pretty confused about the cleanest way about mantaining immutability.

This is the simplest and most stupid I got so far, but I'm still wondering if isn't there a less verbose way to achieve this

class MyClass
{
    public int Property1 { get; }
    public string Property2 { get; }

    public MyClass CopyWith(int? property1 = default, string property2 = default)
    {
        return new MyClass
        {
            Property1 = property1 ?? Property1,
            Property2 = property2 ?? Property2
        };
    }
}

class Program
{
    static void Main()
    {
        var myObj = new MyClass { Property1 = 123, Property2 = "123" };

        var myModifiedObj = myObj.CopyWith(property2: "456");
    }
}

An extension methods with generics would involve A LOT of reflection, but I'm still convince that I'm overthinking and maybe there's a simpler solution that I'm missing...

3 Answers

To have an immutable class you first need to make sure that the properties are not accessible from the outside. So I'd change it to:

class MyClass
{
    public int Property1 { get; }
    public string Property2 { get; }

    public MyClass(int prp1, string prp2)
    {
        Property1 = prp1;
        Property2 = prp2;
    }

    public MyClass With(int? property1 = default, string property2 = default) =>
        new MyClass(property1 ?? Property1, property2 ?? Property2);

}

This is already a good starting point. As you said, using generics might mean to over-engineer the code for the problem you are trying to solve.

Some people prefer to use one method for each property you want to change, chaining the calls if they want to change multiple properties. This might look a bit more verbose but usually results in a cleaner class.

It would look like this:

class MyClass
{
    // until here it is the same as before

    public MyClass WithPrp1(int prp1) => new MyClass(prp1,Property2);
    public MyClass WithPrp2(string prp2) => new MyClass(Property1,prp2);   
}

class Program
{
    static void Main()
    {
        var myObj = new MyClass(123, "123");
        var myModifiedObj = myObj.WithPrp1(456).WithPrp2("789");
    }
}

C# is (very unfortunately) quite limited when it comes to declaring/mutating immutable types - there was talk about immutable records coming to C#8 but that didn't happen.

However you can get what you want reasonably elegantly and efficiently with a combination of an extension method with reflection and caching of delegates for optimization, see project With. The code there is dense but usage is straight forward:

class MyClass : IImmutable
{
  public MyClass(int property1, string property2) { Property1 = property1; Property2 = property2; }
  public int Property1 { get; }
  public string Property2 { get; }
}

void F()
{
  var myObj = new MyClass(123, "s1");
  var myModifiedObj = myObj.With(x => x.Property2, "s2");
}

(another more classic solution to your problem is the Builder Pattern though the result is much more verbose and clunky)

Make your properties setters private, then create an overload method for each property.

public class MyClass
{
    public int Property1 { get; }

    public string Property2 { get; }

    public MyClass(int property1, string property2)
    {
        Property1 = property1;
        Property2 = property2;
    }

    public MyClass CopyWith(int property1) => new MyClass(property1, Property2);

    public MyClass CopyWith(string property2) => new MyClass(Property1, property2);

}
Related