Reputation: 37111
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#.
Upvotes: 8
Views: 2289
Reputation: 1662
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
Upvotes: 16
Reputation: 19315
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);
Upvotes: 0
Reputation: 37111
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.
Upvotes: 0
Reputation: 4950
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);
}
}
Upvotes: 2
Reputation: 2322
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.
Upvotes: 0