LA.NET [EN]

Jul 19

[Update: small update to the implicit operators code. Thanks Kevin]

In a previous post, I’ve mentioned conversion operators. But what is a conversion operator? In the past, I bet that we’ve all needed to convert from one type of object to another. When we’re talking about primitive types, the CLR knows how to perform the conversion (of course, when that is possible). However, when we’re not talking about primitive types, the CLR is only able to perform the conversion if the source object’s type is the same as (or derived from) the target type.

There are other scenarios where we’d like to convert from type A to type B but these types aren’t related. Since the types aren’t “related”, we can’t simply cast from one type to another. In these cases, we’re limited to adapting the API of our types so that they ease the “translation” between types. For instance, suppose we’ve got the following class:

public class A {
    private Int32 _someState;
    public A(){
    }
    public A(Int32 someState){
        _someState = someState;
    }
    public Int32 ToInt32(){
        return _someState;
    }
}

By writing this code, we can “convert” an integer into an A instance (through the constructor) and we can also get an integer from any A instance (by calling the ToInt32 method). Here’s an example:

var fromInteger = 10;
var someInstance = new A(fromInteger);
var wrappedInteger = someInstance.ToInt32();

Defining the methods presented in the class is really a good idea because it means that your class can be consumed from any .NET language. Now, if you’re writing code in a language like C# which supports conversion operators,you can take this a little further and create you own customized conversion operators. The idea is to be able to write code like this:

var fromInteger = 10;
var someInstance = (A)fromInteger;
var wrappedInteger = (Int32) someInstance;

The previous snippet is using an *explicit* conversion operator for converting from and to an integer. An explicit operator is define by a special public and static method where the return type or the parameter must be of the same type as the class where it’s being declared. In order to make the previous code compile,we need to add the following methods to our type:

public static explicit operator A(Int32 anInteger) {
    return new A(anInteger);
}
public static explicit operator Int32(A anA){
    return anA.ToInt32();
}

As you can see, I’ve implement both methods by using the constructor and helper method previously added to the class.  As you can see, both methods return an instance of type A or receive a single parameter of type A. Notice also the use of the explicit keyword.

By annotating our conversion operator methods with that keyword, we’re saying that the compiler is only allowed to use them when it finds an explicit cast. There is also another option here: we can create an implicit conversion operator. We should create an implicit operator whenever there’s no precision lost during the conversion. In our simple example, that never happens, so we can add implicit operators to our classes:

public static implicit operator A(Int32 anInteger){
    return new A(anInteger);
}
public static implicit operator Int32(A anA){
    return anA.ToInt32();
}

Since the implicit and explicit keywords aren’t part of the method signature, then you can’t simultaneously define an explicit and implicit operator for the same type. After adding the operators, you can now run the following code without any errors:

var fromInteger = 10;
A someInstance = fromInteger;
Int32 wrappedInteger = someInstance;

I have used conversion operators in the past and they have improved the readability of my code. For instance, they’re useful when you’re using a fluent builder to create a new instance of a type. Since conversion operators are methods, I guess it would be nice to see the final result of our C# code. Implicit cast operators end up generating methods named op_Implicit, while explicit convertors end up generating op_Explicit methods. Here’s the signature of the IL generated for the implicit operators:

.method public hidebysig specialname static int32 op_Implicit(class DemoProj.Program/A anA) cil managed

.method public hidebysig specialname static class DemoProj.Program/A op_Implicit(int32 anInteger) cil managed

We could, of course, improve our code and add other operators. For instance, lets add an explicit Single conversion operator:

public static explicit operator Single(A anA){
    return anA.ToInt32();
}
public static explicit operator A(Single aSingle){
    return new A((Int32)aSingle);
}

And now, we can write the following code:

var single = (Single) someInstance;
var anA = (A) single;

If you’ve been paying attention and you’ve been writing C# for some time, you’ve probably noticed something strange. How can we write two methods which only differ by return type?

I’ve already said that the explicit/implicit keyword isn’t used in the method signature, but “just to be sure”, you can go ahead and make all conversion methods explicit. After performing this small change, you’ll end up with two methods which differ only in the return type (the explicit convertors from A to Int32 and Single). What’s going on here?

Unlike C#, the CLR does allow you to overload based on the return type. So, if you’re writing IL (anyone?), then you *can* overload methods by return type (though this really isn’t recommended because you won’t be able to consume those types from C#). The C# compiler uses this knowledge and allows you to introduce these overrides when you’re writing operator convertors.

Before ending, you should also notice that there are some things to keep in mind before creating and using conversion operators:

  • you cannot create custom conversions for interfaces or for the Object type.
  • conversion operators are not executed when using the is and as operators.

And I guess that’s it for now. Stay tuned for more basics.

1 comment so far

  1. Kevin Watkins
    9:53 am - 7-21-2010

    Won”t your implicit example:

    var fromInteger = 10;
    var someInstance = fromInteger;
    var wrappedInteger = someInstance;

    Actually make fromInteger, someInstance and wrappedInteger all integer variables because you are using var? Shouldn”t the correct example be:

    var fromInteger = 10;
    A someInstance = fromInteger;
    Int32 wrappedInteger = someInstance;

Leave a Reply

Your email address will not be published. Required fields are marked *


*

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>