Trying to combine functional style (immutable objects) and flexibility of property setters. For the sake of example, let’s say we have a soil types table with two attributes: Color and Name. I am looking for a way to alternate Names, but not Color. Here is how I solved it:
// retrieving: all objects are immutable
SoilTypes types = SoilTypes.Default;
ISoilType clay1 = types.Clay;
ISoilType clay2 = types[3];
// derive an alternated immutable copy
SoilTypes altTypes = types
.With(tt =>
{
// tt.SensitiveFines.Color is still read only
tt.SensitiveFines.Name = "Very sensitive fines!";
tt[2].Name = "Purely Organic soil!";
});
// retrieving: everything is immutable
ISoilType sensitiveFines = altTypes.SensitiveFines;
Where this interface is immutable:
public interface ISoilType
{
Color Color { get; }
string Name { get; }
}
And this class is mutable:
public class SoilType : ISoilType
{
public static implicit operator SoilType((Color Color, string Name) tuple) =>
new SoilType(tuple.Color, tuple.Name);
internal SoilType(ISoilType source)
: this(source.Color, source.Name)
{
}
internal SoilType(Color color, string name)
{
Color = color;
Name = name;
}
public Color Color { get; }
public string Name { get; set; }
}
And this non-generic class is immutable:
public class SoilTypes : SoilTypes<ISoilType>
{
public static SoilTypes Default = new SoilTypes(
(White, "Undefined"),
(Red, "Sensitive Fines"),
(Green, "Organic Soil"),
(Blue, "Clay"),
(Orange, "Silty Clay"));
public SoilTypes(params SoilType[] types)
: base(types)
{
}
public SoilTypes With(Action<SoilTypes<SoilType>> update)
{
var copy = this
.Select(t => new SoilType(t))
.ToArray();
update(new SoilTypes<SoilType>(copy));
return new SoilTypes(copy);
}
}
while this generic base used in both situations:
public class SoilTypes<TType> : ReadOnlyCollection<TType>
where TType : ISoilType
{
internal SoilTypes(TType[] types)
: base(types)
{
}
public TType Undefined => this[0];
public TType SensitiveFines => this[1];
public TType OrganicSoil => this[2];
public TType Clay => this[3];
public TType SiltyClay => this[4];
}
// tt.SensitiveFines.Color is still read only
-- could you elaborate what you mean/wht is Color "read only"? I don't see how the Color attribute is different from the Name in the code provided. \$\endgroup\$SoilType
class does not have a setter forColor
. \$\endgroup\$Name
in a semi-mutable-immutable fashion? Btw. theSoilTypes
should beSoilTypeCollection
. Collections do not have plural names ;-] \$\endgroup\$SoilTypes.Default.Clay.Name = "Dirty thing"
but do allow to writeSoilTypes.Default.With(alt => alt.Clay.Name = "Dirty thing")
to derive and overrideSoilTypes
content, so we combine immutability with the syntactical efficiency of property assignments. My API is way wider then 5x2 table, so I do need it. P.S. I feel guilty aboutSoilTypes
name, but it is how domain experts reference it - it is not just a technical artifact (container) - it is actually a business object. \$\endgroup\$