Cast to and from the underlying type
This page describes casting to and from the underlying primitive, using either explicit or implicit casting operators. However, it is not recommended to use implicit casting. We'll first describe how to use them and then describe why the problems with implicit casts.
To generate casts, set one of the ...PrimitiveCasting
flags in the constructor of the ValueObject
attribute.
You can specify casting to the primitive, or from the primitive. Here's an example of implicitly casting to and from the primitive.
Vogen defaults to generating explicit casts both ways (to and from the primitive), which means you can do things like this:
Explicitly casting from the primitive (var x= (Age)42
) is the same as calling `Age.From(42); any validation and normalization methods are called.
You could also specify implicit casting:
This allows you to do things like:
Implicit casting bypasses any validation and normalization methods.
That's how to use both of them, so why shouldn't you use implicit casts?
Why shouldn't I use them?
Using implicit casts might seem like a handy way to use the underlying primitive natively, but the goal of strong-typing primitives is to differentiate them from the underlying type.
Take, for instance, a Score
type:
An implicit cast would allow easy access to the Value
, allowing things such as int n = _score + 10;
.
But what would be preferable is to be more explicit and add a method that describes the operation, something like:
This is more explicit. It says that scores can be increased but not decreased (of course, Points
could allow negatives, but that's up to you and your domain).
There's no denying that implicit operators can be useful. But as useful as they are, they can confuse things and potentially introduce errors, for example:
We can see that we lose type-safety. An Age
is implicitly an int, and an OsVersion
is implicitly an int. Therefore, the call to Print
, which takes an OsVersion
, also, implicitly, takes an int
!
There are also issues with validation that violates the rules of implicit operators, which is:
But a primitive cast to a value object might not always succeed due to validation.
Other opinions, such as the guidelines listed in this answer say:
If the conversion can throw an
InvalidCastException
, then it shouldn't be implicitIf the conversion causes a heap allocation each time it is performed, then it shouldn't be implicit
If Vogen did throw during an implicit cast, it wouldn't throw an InvalidCastException
(only an ValueObjectValidationException
). Also, for classes, it would create a heap allocation (but value objects can be a class
or struct
, so it wouldn't make sense to allow them one and not the other).
As mentioned previously, validation is skipped for implicit casts (because they should never throw an exception). Let's say there's a type like this:
That says that Age
instances can never be negative, but primitive casting gets around this:
The implicit cast in var age10 = age20 / 2
results in an int
and not an Age
. Changing it to Age age10 = age20 / 2
fixes it. But this does go to show that it can be confusing.
For the reasons above, it is recommended not to use implicit casts. When the urge to use them arises, it is worth thinking what explicit concept is missing from the code, for example, Score.IncreaseBy
.