User-Defined Conversions

User-Defined Conversions

The Crash Course

  • User-defined conversions are a way for you to define how the C# compiler and runtime should convert to and from classes that you have created.
  • An implicit cast or conversion is one where the change is done without needing the conversion operator, while an explicit cast or conversion requires it. For example, there's an implicit cast from float to double: float a = 3; double b = a; The value of a is automatically converted to a double. There's an explicit cast from double to float: double a = 3; float b = (float)a;. The value of a can be converted to a float, but you must explicitly state that you are making the conversion.
  • To make a user-defined conversion, you follow this pattern: static public implicit operator MagicNumber(int value) { /* do your conversion here, returning a MagicNumber */ };
  • You can use the implicit or explicit keywords to indicate whether it will be an implicit conversion or an explicit conversion.

Introduction

Two tutorials ago, we looked at operator overloading, which provided a way to make operators function in a special way for our class. In the last tutorial, we looked at how to do a similar thing for the indexing operator. In this tutorial, we'll take a look at how to custom define how our class can be converted (or casted) into a different type.

By the way, in this tutorial, when I use the word "cast" and "conversion," I'm referring to the same thing—converting from one type to another.

Implicit vs. Explicit Conversions

Before we look at user-defined conversions, we need to briefly discuss how conversions work in general.

Let's take a quick look at the float and double types. If we have a float, we can simply use it as a double, and our program will automatically convert it into a double for us, as shown below:

float a = 3.5f;
double b = a; // the value in 'a' is automatically turned into a double when it is assigned to 'b.'

On the other hand, though, if we try the reverse, we get a compile-time error:

double b = 3.5;
float a = b; // this won't compile because you're trying to convert a double to a {{float}}.

The reason this doesn't work is that you're trying to squeeze a double into something the size of a float, which has a lot less precision and accuracy. You're losing information.

However, there is a conversion for it, you just have to explicitly state that this is what you want to do:

double b = 3.5;
float a = (float)b;

This tells the compiler that we do want to convert it to a float, knowing that we're probably losing information in the process.

This shows us the difference between an implicit cast or conversion and an explicit cast. When we try to convert from a float to a double, it is implied that it can simply be changed over, and we don't need to do anything fancy to change it. With an explicit cast, we have to actually state, "yes, I know what I'm doing, and I want to convert this over to a different type." And we do this with the conversion/cast operator ( and ), with the type in between the parentheses, as we saw in that last example.

In a second, when we create our own custom conversions, we'll need to specify whether the cast is implicit or explicit, as we've looked at here.

Creating a User-Defined Conversion

To illustrate how this works, let's look at a simple MagicNumber class that is basically just pairing up a plain old number with an IsMagic property:

public class MagicNumber
{
    public int Number { get; set; }
    public bool IsMagic { get; set; }
}

This is a simple class, storing a number as an int, and a bool that indicates whether the number is "magic" or not (whatever that happens to mean).

But we can probably see that it is entirely possible to imagine how we may want to take a regular old int, and convert it to a MagicNumber, and also take a MagicNumber and convert it to an int. For something like this, we can easily add in a tiny piece of code to enable conversions.

In the first case, where we convert an int to a MagicNumber, we can probably do this implicitly (no need to cast it with (MagicNumber)), and just bring the int in, and make IsMagic false:

static public implicit operator MagicNumber(int value)
{
    return new MagicNumber() { Number = value, IsMagic = false };
}

Like with all other operators, this must be marked static and public. We then specify whether it is implicit or explicit, though in this case, we've chosen implicit. We then put down the type that is being converted to (MagicNumber, in this case), and then in parentheses, what is being converted from. Inside the body of the conversion, we write code to convert from one to the other.

Now, with this added to our MagicNumber class, we can simply convert from ints to MagicNumbers:

int aNumber = 3;
MagicNumber magicNumber = aNumber;

We can also create an explicit cast in the same way. Here, we'll define a conversion going in the opposite way, from MagicNumber to int. It is typically a good idea to require an explicit cast whenever you're losing information in the conversion process.

To make an explicit cast, we would add something like the following to our class:

static public explicit operator int(MagicNumber magicNumber)
{
    return magicNumber.Number;
}

Like before, we're required to mark it both static and public. We use the explicit keyword to indicate that to use this, you actually have to explicitly state you want to use the conversion using the conversion operator ( and ). We also use the operator keyword, and like before, indicate the type we're converting to, and inside of the parentheses, the type we're converting from.

Inside of the conversion block, we write code to actually convert. In this case, I'm just taking the MagicNumber's number and leaving behind the IsMagic part. So we lose information about whether the number was magic or not (whether IsMagic was set to true or not).

Now, elsewhere in our code, we can use this conversion, but this time, we'll have to go out of our way to explicitly state that we want to convert from one type to another:

MagicNumber magicNumber = new MagicNumber() { Number = 3, IsMagic = true };
int aNumber = (int)magicNumber;

What's Next?

User-defined conversions are a nifty little thing you can do to make code cleaner and more understandable and can be very powerful when used correctly.

Our next topic of conversation is going to be extension methods, which is a feature of the C# language that lets you basically add methods onto a class that you didn't create and that you don't have the ability to directly modify or add on to.