The simplest way to "disconnect" things is for them to be value types in the first place; if x
is some kind of struct
, then what you have will work already, however: mutable structs is usually a terrible idea and causes all kinds of confusion, so in reality I'd go with a different API there:
var x = new SomeReadonlyValueType(10);
var y = x; // this is a value copy
y = y.WithLength(x.len * 2); // mutation method
If you don't want to go that way, and you want x
to be an instance of a mutable class, you'd need to deep-clone the instance instead, i.e.
x.len=10;
val y=x.DeepClone();
y.len=x.len*2;
Here, the point where they become separate is very clear and explicit; you'd need to implement DeepClone()
, though - there is no standard inbuilt way of providing that. For simple cases, hand-rolled is fine; for complex cases, serialization is a popular way of doing it.
Example code for SomeReadonlyValueType
:
readonly struct SomeReadonlyValueType
{
public int Length { get; }
public int Width { get; }
public SomeReadonlyValueType(int length, int width)
{
Length = length;
Width = width;
}
public override string ToString() => $"{Length} x {Width}";
public SomeReadonlyValueType WithLength(int length)
=> new SomeReadonlyValueType(length, Width);
public SomeReadonlyValueType WithWidth(int width)
=> new SomeReadonlyValueType(Length, width);
}