8

I am writing code in a functional style in C#. Many of my classes are immutable with methods for returning a modified copy of an instance.

For example:

sealed class A
{
    readonly X x;
    readonly Y y;

    public class A(X x, Y y)
    {
        this.x = x;
        this.y = y;
    }

    public A SetX(X nextX)
    {
        return new A(nextX, y);
    }

    public A SetY(Y nextY)
    {
        return new A(x, nextY);
    }
}

This is a trivial example, but imagine a much bigger class, with many more members.

The problem is that constructing these modified copies is very verbose. Most of the methods only change one value, but I have to pass all of the unchanged values into the constructor.

Is there a pattern or technique to avoid all of this boiler-plate when constructing immutable classes with modifier methods?

Note: I do not want to use a struct for reasons discussed elsewhere on this site.


Update: I have since discovered this is called a "copy and update record expression" in F#.

Community
  • 1
  • 1
sdgfsdh
  • 33,689
  • 26
  • 132
  • 245
  • 3
    I'd question the need to have these "setters" as you've written them for everything. Really you should be performing complex operations on the type, not just "setting" a value arbitrarily. For just a general pair, it needs nothing other than a constructor. – Servy Jul 25 '16 at 19:09
  • 1
    @Servy Sorry, this is a limitation of the example. Most modifiers are more complex, or task specific. Please imagine I gave a better example :) – sdgfsdh Jul 25 '16 at 19:10
  • 4
    Yeah. Use F#. Seriously now. C# is first and foremost stateful OOP language. While it does have some functional features, it is far from being good functional language. – Euphoric Jul 25 '16 at 19:14
  • I haven't used F#. But if it makes it easier to build classes that way you could just build your models in F# and do the rest in C#. – Scott Hannen Jul 25 '16 at 19:21
  • You can try to generate set methods statically with text template [generator tool](https://msdn.microsoft.com/en-us/library/bb126445.aspx). – Andrey Burykin Jul 25 '16 at 19:23
  • 2
    There is not much in the language today to syntactically sweeten building "with" methods on immutable types. If you look at the Roslyn source code you'll see that there are just lots and lots of such methods with boilerplate code in them. As Andrey suggests in the comment above, the boilerplate was generated by a tool; this in itself suggests that there is a possible missing feature of the language. The design team is aware of the shortcoming; consider participating in the Roslyn forum on github if you want to advocate for features to improve this being added to possible future versions. – Eric Lippert Jul 25 '16 at 19:50
  • 1
    As already mentioned, create your classes with F# and then use them from C# if you want. – Sergey.quixoticaxis.Ivanov Jul 25 '16 at 20:12
  • Related: http://stackoverflow.com/questions/18151969/can-we-get-access-to-the-f-copy-and-update-feature-from-c – sdgfsdh Jul 26 '16 at 13:32

5 Answers5

14

For larger types I will build a With function that has arguments that all default to null if not provided:

public sealed class A
{
    public readonly X X;
    public readonly Y Y;

    public A(X x, Y y)
    {
        X = x;
        Y = y;
    }

    public A With(X X = null, Y Y = null) =>
        new A(
            X ?? this.X,
            Y ?? this.Y
        );
}

Then use the named arguments feature of C# thus:

val = val.With(X: x);

val = val.With(Y: y);

val = val.With(X: x, Y: y);

I find int a much more attractive approach than lots of setter methods. It does mean that null becomes an unusable value, but if you're going the functional route then I assume you're trying to avoid null too and use options.

If you have value-types/structs as members then make them Nullable in the With, for example:

public sealed class A
{
    public readonly int X;
    public readonly int Y;

    public A(int x, int y)
    {
        X = x;
        Y = y;
    }

    public A With(int? X = null, int? Y = null) =>
        new A(
            X ?? this.X,
            Y ?? this.Y
        );
}

Note however, this doesn't come for free, there are N null comparison operations per call to With where N is the number of arguments. I personally find the convenience worth the cost (which ultimately is negligible), however if you have anything that's particularly performance sensitive then you should fall back to bespoke setter methods.

If you find the tedium of writing the With function too much, then you can use my open-source C# functional programming library: language-ext. The above can be done like so:

[With]
public partial class A
{
    public readonly int X;
    public readonly int Y;

    public A(int x, int y)
    {
        X = x;
        Y = y;
    }
}

You must include the LanguageExt.Core and LanguageExt.CodeGen in your project. The LanguageExt.CodeGen doesn't need to included with the final release of your project.

The final bit of convenience comes with the [Record] attribute:

[Record]
public partial class A
{
    public readonly int X;
    public readonly int Y;
}

It will build the With function, as well as your constructor, deconstructor, structural equality, structural ordering, lenses, GetHashCode implementation, ToString implementation, and serialisation/deserialisation.

Here's an overview of all of the Code-Gen features

louthster
  • 1,560
  • 9
  • 20
  • this will not work for fields you want to set (with) to null – kofifus Aug 29 '18 at 21:44
  • @kofifus, perhaps read the answer? I explicitly state that, and the reasons why it’s really not a big deal. If you need and optional value, use an Option type and get tons of type safety too – louthster Aug 30 '18 at 17:55
  • I don't see where you mention that, if X is a reference type (ie MyClass) and I want to 'With' it to null I can't – kofifus Aug 31 '18 at 00:31
  • 1
    "It does mean that null becomes an unusable value, but if you're going the functional route then I assume you're trying to avoid null too and use options." – louthster Sep 03 '18 at 16:32
2

For this exact case I am using Object. MemberwiseClone(). The approach works for direct property updates only (because of a shallow cloning).

sealed class A 
{
    // added private setters for approach to work
    public X x { get; private set;} 
    public Y y { get; private set;} 

    public class A(X x, Y y) 
    { 
        this.x = x; 
        this.y = y; 
    } 

    private A With(Action<A> update) 
    {
        var clone = (A)MemberwiseClone();
        update(clone);
        return clone;
    } 

    public A SetX(X nextX) 
    { 
        return With(a => a.x = nextX); 
    } 

    public A SetY(Y nextY) 
    { 
        return With(a => a.y = nextY); 
    } 
 }
dadhi
  • 4,807
  • 19
  • 25
  • It's not too great that your fields (now properties) are no longer `readonly`. With few members, it will also have more code than OP's version, and performance is slightly worse too (`MemberwiseClone` is slightly slower by itself, additionally it copies at least one field too much, that delegate also needs to be allocated, invoked and collected). Perhaps all irrelevant points for most applications, but good to keep in mind if one is designing a system from scratch. – vgru Jul 26 '16 at 07:36
  • 1
    The property with private setter can be considered "readonly" (not talking about reflection). The performance should be considered in wider context. In case it matters, you may inline the `With` method avoiding lambda allocation. The code will become more tedious, but still less error-prone than the manual coding. Actually in my project I am using the inlined version for class with more than 10 properties, and the benefits are evident. – dadhi Jul 26 '16 at 07:46
0

You may use following pattern (don't know if it pass, but you asked for less redundant version, anyway you may get an idea):

 public class Base
    {
        public int x { get; protected set; }
        public int y { get; protected  set; }

        /// <summary>
        /// One constructor which set all properties
        /// </summary>
        /// <param name="x"></param>
        /// <param name="y"></param>
        public Base(int x, int y)
        {
            this.x = x;
            this.y = y;
        }

        /// <summary>
        /// Constructor which init porperties from other class
        /// </summary>
        /// <param name="baseClass"></param>
        public Base(Base baseClass) : this(baseClass.x, baseClass.y)
        {
        }

        /// <summary>
        ///  May be more secured constructor because you always can check input parameter for null
        /// </summary>
        /// <param name="baseClass"></param>
        //public Base(Base baseClass)
        //{
        //    if (baseClass == null)
        //    {
        //        return;
        //    }

        //    this.x = baseClass.x;
        //    this.y = baseClass.y;
        //}
    }

    public sealed class A : Base
    {
        // Don't know if you really need this one
        public A(int x, int y) : base(x, y)
        {
        }

        public A(A a) : base(a)
        {
        }

        public A SetX(int nextX)
        {
            // Create manual copy of object and then set another value
            var a = new A(this)
            {
                x = nextX
            };

            return a;
        }

        public A SetY(int nextY)
        {
            // Create manual copy of object and then set another value
            var a = new A(this)
            {
                y = nextY
            };

            return a;
        }
    }

This way you decrease amount of parameters in constructor of A by passing reference of existing object, set all properties and set then only one new inside some A method.

Anton Norko
  • 2,166
  • 1
  • 15
  • 20
  • 1
    But now you're mutating types outside of their constructors. – Servy Jul 25 '16 at 19:36
  • Yes, is it really bad for you? You asked about less redundant pattern to reduce amount of parameters in constructor. Sorry, if I misunderstood you. – Anton Norko Jul 25 '16 at 19:46
  • The problem with this answer is that if you are going to make things mutable, then there is an easier way to implement this: just clone the whole object and set the fields you want to change. – sdgfsdh Jul 25 '16 at 19:53
  • @sdgfsdh: which is what this answer demonstrates. Cloning merely calls the copy-ctor. – GreatAndPowerfulOz Jul 25 '16 at 20:02
  • I believe that `var a = new A(){ X = 1 }` is sugar for `var a = new A(); a.X = 1;`. It requires mutability; it is not part of the constructor. – sdgfsdh Jul 25 '16 at 20:10
  • 3
    @Great.And.Powerful.Oz No, that's a constructor followed by several assignments. – Servy Jul 25 '16 at 20:10
0

I would use the builder pattern in combination with some extensions methods. The basic idea is to have a ToBuilder method to initialize an A into an ABuilder, modify the builder using a fluent interface, then complete the builder to get the new instance. This approach may even reduce garbage in some cases.

The immutable class:

public sealed class A
{
    readonly int x;

    public int X
    {
        get { return x; }
    }

    public A(int x)
    {
        this.x = x;
    }
}

The builder class:

public sealed class ABuilder
{
    public int X { get; set; }

    public ABuilder(A a)
    {
        this.X = a.X;
    }

    public A Build()
    {
        return new A(X);
    }
}

Useful extension methods:

public static class Extensions
{
    public static ABuilder With(this ABuilder builder, Action<ABuilder> action)
    {
        action(builder);

        return builder;
    }

    public static ABuilder ToBuilder(this A a)
    {
        return new ABuilder(a) { X = a.X };
    }
}

It is used like this:

var a = new A(10);

a = a.ToBuilder().With(i => i.X = 20).Build();

It's not perfect. You need to define an extra class with all of the properties of the original, but the usage syntax is quite clean and it maintains the simplicity of the origin type.

sdgfsdh
  • 33,689
  • 26
  • 132
  • 245
0

There is an elegant efficient solution to this - see project With

With With your class can simply become:

sealed class A : IImmutable 
{
    public readonly X x;
    public readonly Y y;

    public class A(X x, Y y)
    {
        this.x = x;
        this.y = y;
    }
}

and you can do:

using System.Immutable;
var o = new A(0, 0);
var o1 = o.With(a => a.y, 5);
kofifus
  • 17,260
  • 17
  • 99
  • 173