Generic universal classes in C #

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

For instance:

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 larger class in which many other members participate.

The problem is that building these modified copies is a lot. Most methods change only one value, but I have to pass all immutable values ​​to the constructor.

Is there a sample or technique to avoid all this boiler plate when building immutable classes using modifier methods?

Note. I do not want to use the struct arguments for which were discussed elsewhere on this site .


Update: I have since discovered that this is called the β€œcopy and update” expression in F #.

+6
source share
5 answers

For larger types, I will create a With function that has arguments that default to null unless otherwise specified:

 public sealed class A { public readonly XX; public readonly YY; public A(X x, Y y) { X = x; Y = y; } public A With(XX = null, YY = null) => new A( X ?? this.X, Y ?? this.Y ); } 

Then use the C # named argument function this way:

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

I think the approach is much more attractive than many setter methods. This means that the null value becomes unusable, but if you go along the functional route, then I assume that you are trying to avoid null and use parameters.

If you have type values ​​/ structures as members, then make them Nullable in 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, that this is not free; there are N empty comparison operations on a With call, where N is the number of arguments. Personally, I believe that convenience is worth the cost (which is ultimately insignificant), however, if you have something that is especially sensitive to performance, you should resort to special installation methods.

If you find the boredom 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 this:

 [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 LanguageExt.Core and LanguageExt.CodeGen in your project. LanguageExt.CodeGen does not need to be included in the final version of your project.

+12
source

For this exact case, I am using Object. MemberwiseClone() Object. MemberwiseClone() . This approach only works for direct property updates (due to 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 => ax = nextX); } public A SetY(Y nextY) { return With(a => ay = nextY); } } 
+2
source

You can use the following template (you don’t know if it will pass, but you asked for a less redundant version, in any case you can 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 reduce the number of parameters in constructor A by passing a reference to an existing object, set all the properties and set only one new one inside some method A.

0
source

I would use a build pattern in combination with some extension methods. The main idea is for the ToBuilder method ToBuilder initialize A in ABuilder , modify the builder using the free interface, and then complete the builder to get a new instance. In some cases, this approach can even reduce the amount of garbage.

Immutable class:

 public sealed class A { readonly int x; public int X { get { return x; } } public A(int x) { this.x = x; } } 

Builder Class:

 public sealed class ABuilder { public int X { get; set; } public ABuilder(A a) { this.X = aX; } 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 = aX }; } } 

It is used as follows:

 var a = new A(10); a = a.ToBuilder().With(i => iX = 20).Build(); 

This is not perfect. You need to define an additional class with all the properties of the original, but the usage syntax is pretty clean and supports the simplicity of the source type.

0
source

There is an elegant, effective solution for this - see Design with

With With your class you 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 => ay, 5); 
0
source

All Articles