How to define a cast operator in C#
In this article I’m going to explain what are cast operators, how to define them, some pitfalls, and if we really need them.
What are cast operators?
Cast operators, or conversion operators (which is a more appropriate name in C#) are ways that the language provides to convert one data type to another.
These conversions happen in statements: by doing arithmetic, an assignment, or by passing the value to a function. And can occur either implicitly or explicitly.
One example of an implicit conversion is when we try to assign an int
value to a double
one:
int foo = 10;
double bar = foo;
Here, foo
is converted implicitly to a double which makes possible the assignment. This happens automatically since it’s a widening conversion; meaning that the value being assigned occupies less memory (bytes) than the value to which is assigned. So it’s perfectly safe to make this assignment, since there’s no risk of losing data or precision (integer types takes up 32 bits in memory, and double are 64 bits).
But what if we need to convert a bigger type to a smaller one? For example, a double
to int
:
double foo = 11.3;
int bar = foo;
That would cause a compiler error, showing an error with the form of “Can’t convert implicitly the type ‘double’ to ‘int’”. The reason is because if the compiler had made that conversion it will result in a loss of data (narrowing conversion), because doubles’
range are bigger than int
, and integers can’t represent decimal point values.
So what we need is a way to tell the compiler that we know what we’re doing and let us convert the type. This is where conversion operators comes in handy.
Conversion operators has the form of: (typeToConvert) value.
So if we need to make last example legal, we need to use the explicit conversion:
double foo = 11.3;
int bar = (int)foo;
Here, foo
is casted (converted) to an int
type and then assigned to bar
variable.
So, to resume a bit: Implicit conversions happens when there’s no risk of losing data, nor throwing exceptions during the conversion. And explicit ones are those we specify in a form of a cast and can result on losing precision.
How to overload a conversion operator.
Let’s say we have a Month class, representing all the months from “January” through “December.” Something like this:
class Month
{
string[] _items = {
"January", "February", "March",
"April", "May", "June",
"July", "August", "September",
"October", "November", "December"
};
}
Note that here we could use an static readonly array of strings, or even better; an enumeration instead of this. Our example might not have the best design choices, but the purpose here is demonstrate how to overload the conversions so bear with me.
Now, what if we want to store the month in a DB or a file?, mapping the months to an int
value might be a good idea since we could save space and improve performance (or maybe to store it as a Foreign Key). So the month “January” is going to be the number 1, “February” is 2, and so on.
One way to achieve this is through overloading conversion operators.
Conversion operators have the following syntax:
accessModifier static explicitOrimplicit operator returnType(convertFrom)
{
// …
}
And have the following properties:
- Conversions with the
implicit
keyword occur automatically when it is required. - Conversions with the
explicit
keyword needs a cast to make the conversion. - They all need the static modifier.
- Either (but not both) the returned value or the converted from value must be the containing type.
So to declare a conversion from Month
type to an int
, we can do the following:
public static implicit operator int(Month m)
{
for (int i = 0; i < m._items.Length; ++i)
{
if (m._items[i] == m.Current)
{
return i + 1;
}
}
return 0;
}
The important part here is the signature of the overlading. The cast is marked as implicit, so making an assignment from Month
type to int
is possible without a cast:
var month = new Month();
month.Current = "February";
int bar = month;
Console.WriteLine("Month int is: {0}", bar);
Will print: “Month int is: 2”.
In my opinion, here it’s better to use implicit rather than explicit because all the Month
values can be converted to int, even if the Month
hasn’t been initialized (therefore containing an invalid Current
value.) In that case we return a zero if the Month
is invalid.
Now, how do we convert an int
to a Month
?
Since all Months can be represented in integers, but not all integer’s values can represent a Month
, it’s better to use an explicit conversion.
We define it as the following:
public static explicit operator Month(int m)
{
var month = new Month();
if (m > 0 && m <= month._items.Length)
{
month._current = month._items[m - 1];
return month;
}
return null;
}
Note that if we provide an unvalid int –that is, outside the range of Month values– (< 1 or > 12) we return a null instead a Month
.
Now we cast the int
specified to a month
.
int m = 7;
var month2 = (Month)m;
if (month2 != null) {
Console.WriteLine("int to Month is: {0}", month2.Current);
} else {
Console.WriteLine("Fail to convert int to Month type");
}
That will print: “int to Month: July”
You can check the full code here
Do we really need to overload conversion operators?
It’s a matter of choice, but I rather declare functions to convert those values. For example if we need to convert the Month
value to an integer I would define a Month.ToInt()
function. For achieving the reverse thing I’ll probably declare a constructor or property (with validations) that accepts an int
value.