31
loading...
This website collects cookies to deliver better user experience
public record TemperatureCelsius
{
private readonly Decimal _value;
public Decimal Value
{
get => _value;
init => _value = value < -273.15m ? throw new ArgumentOutOfRangeException() : value;
}
}
public record Temperature
{
public Temperature(Decimal degrees, TemperatureUnit unit)
{
switch (unit)
{
case TemperatureUnit.Celsius when degrees < -273.15m: throw new ArgumentOutOfRangeException();
case TemperatureUnit.Fahrenheit when degrees < -459.67m: throw new ArgumentOutOfRangeException();
case TemperatureUnit.Kelvin when degrees < 0m: throw new ArgumentOutOfRangeException();
}
Degrees = degrees;
Unit = unit;
}
public Decimal Degrees { get; private init; }
public TemperatureUnit Unit { get; private init; }
}
Degrees
is then initialized before Unit
, we cannot perform a meaningful check for undercutting the zero point.Temperature
') is suboptimal for a reallife application. If possible, only measurement unit should be used within a context. The conversion to other units then happens at the context boundaries.)with
-operator can be used to create a new record with modified properties. The initializers are called for the properties that are set within the block and the values are copied for all other properties. This means that the validations we implemented in the initializer will be applied automatically. So we don't have to care about that use case during the implementation.with
-operator is called.// Using the type 'Temperature' from the example above.
var celsius = new Temperature(34m, TemperatureUnit.Celsius);
var kelvin = celsius with { Unit = TemperatureUnit.Kelvin }; // Error CS0272: Property has no accessible setter.
int
, string
, ...). Then you just have to make sure that the properties and backing-fields are read-only. You achieve this by marking your fields with readonly
and assure that properties have none or private setters.private readonly int[] array;
public void Example()
{
array[1] = 2; // Assignment is possible
array = new int[3]; // Error CS0191: Cannot assign to read-only field
}
readonly
. This ensures that no method can change the state of the object.Equals
and GetHashCode
method for a record that takes all properties of two instances into account. However, this only works if all properties also support value equality. This is the case for primitive data types and (correctly implemented) Value objects. As a container, we may only use data structures that support value equality.ImmutableArray
class shows.public record ColorGradient(System.Collections.Immutable.ImmutableArray<Color> Colors);
[Fact]
public void Equality()
{
var gradient1 = new ColorGradient(new[] { Color.Black, Color.Green, Color.White }.ToImmutableArray());
var gradient2 = new ColorGradient(new[] { Color.Black, Color.Green, Color.White }.ToImmutableArray());
Assert.NotEqual(gradient1, gradient2); // System.Collections.Immutable.ImmutableArray does not support value-equality.
}
ColorGradient2
behaves as expected.public record ColorGradient2(ValueArrays.ValueArray<Color> Colors);
[Fact]
public void Equality()
{
var gradient1 = new ColorGradient2(new ValueArrays.ValueArray<Color>(new[] { Color.Black, Color.Green, Color.White }));
var gradient2 = new ColorGradient2(new ValueArrays.ValueArray<Color>(new[] { Color.Black, Color.Green, Color.White }));
Assert.Equal(gradient1, gradient2);
}
Equals
or GetHashCode
(and tests for them). Therefor you can fully focus on the implementation of your business logic...