Avoid Conversion Operators

In this article I will examine the conversion operators in C# and focus concretely on a specific usage scenario which can lead to subtle bugs in your application. I’ve recently seen such a problematic piece of code so decided to share my experience.

Conversion operators in C# provide a mechanism for types substitutability meaning that one type can be passed in where another type is expected. Typically, in OOP we achieve that via polymorphism e.g. if class B inherits from class A , B is a specialization of A and we can substitute A for B . Conversion operators seemingly achieve the same goal but can be dangerous in particular circumstances.

Here’s an example of a conversion operator. Below we have two classes – A and B with B being implicitly convertible to A :

public class A { public int Id { get; set; } public static implicit operator A(B source) // Conversion operator { return new A { Id = source.Id }; } } public class B { public int Id { get; set; } }

We can now pass an object of type B everywhere an object of type A is expected. This can be problematic if object A ’s state gets mutated. To demonstrate this, let’s define the following method:

public static void SetId(A obj, int id) { obj.Id = id; }

This is probably not the most useful method you’ve seen but it’s a very simple setup to show the problematic behavior. Now, suppose we have the following driver code:

static void Main(string[] args) { var myObj = new B(); SetId(myObj, 1); Console.WriteLine(myObj.Id); // 0 }

It’s quite normal by just looking at this code to expect it to output a value of 1 . This is not the case, however, and the result we get is 0 , so for some reason the Id field of myObj does not get modified in the SetId method. With such a simple example it may not take you long until you figure out what’s wrong here. The reason myObj is not changed is that when passing an object of type B to a method expecting an object of type A , the conversion operator will be invoked which creates a new object of type B and this is what gets passed to the SetId method. To put it in another way, myObj is not passes to SetId but a temporary object produced by the conversion operator. This temp object is alive only during the SetId execution and becomes garbage immediately after that.

To fix this, and more importantly – avoid such scenarios in future, I think it’s better if we implement the type conversion more explicitly. One approach would be to simply create a new constructor overload for producing A objects from B objects:

public class A { public A(B source) { this.Id = source.Id; } // Rest of class A }

Let’s now see how it looks if someone produces similar client code as the previous one but this time creating the A object from B via A ’s constructor:

static void Main(string[] args) { var myObj = new B(); SetId(new A(myObj), 1); Console.WriteLine(myObj.Id); // 0 }

Now it’s immediately apparent that SetId will work on a temporary object and no one would expect myObj to get modified. The potential bug can be spotted at first glance! Most developers in this case would just assign the new A object to a local variable and pass that into the function so the state changes are reflected after the method returns. Like so:

static void Main(string[] args) { var myObj = new B(); var fromB = new A(myObj); SetId(fromB, 1); Console.WriteLine(fromB.Id); // 1 }

In most cases, I would usually take a step further and instead of a constructor overload I would use a static factory method. But I guess this depends more on the specific use case and context.

For completeness, I should mention that conversion operators can also be defined as explicit . In our example this just means changing the implicit keywork with explicit :

public static explicit operator A(B source)

The effect this will have is that the user will be forced to add a cast to the SetId call:

SetId((A)myObj, 1);

The original problem remains though. A temp object would still be created by the conversion operator and thrown away right after SetId returns.

Summary

In this article I’ve introduced the conversion operators in C# and demonstrated how they can cause problems under certain conditions.

Thanks for reading.

Resources