paint-brush
Are Implicit Operators a Path to Clean Code or a Buggy Nightmare?by@devleader
14,304 reads
14,304 reads

Are Implicit Operators a Path to Clean Code or a Buggy Nightmare?

by Dev LeaderAugust 4th, 2023
Read on Terminal Reader
Read this story w/o Javascript
tldt arrow

Too Long; Didn't Read

Implicit operators are a C# feature that can make our code more readable and expressive. But beware! Misuse can backfire and cause a great deal of headaches!
featured image - Are Implicit Operators a Path to Clean Code or a Buggy Nightmare?
Dev Leader HackerNoon profile picture


Welcome to the world of implicit operators in C#! Implicit operators are a powerful feature of the C# language that has the ability to make our code more readable and expressive. In the wrong hands, they can allow for some really tricky behavior that’s hard to notice unless you go investigating. Implicit operators can allow us to define custom conversions that occur without explicit casting, and thus eliminate some clunky syntax when we want to convert between types. This might seem like a small thing, but it can have a big impact on the readability and maintainability of our code.


Implicit operators are defined using the implicit keyword and can be used to create a method that converts one type to another. Two key rules of implicit operators are that they should not throw exceptions and should not lose information, as the conversion is done automatically by the compiler. This is because the compiler needs to be able to guarantee that the conversion can always succeed without any potential for data loss or exceptions.


Understanding Implicit Operators

Implicit operators are special methods we can define in our classes to allow for automatic conversion to another type. They are defined using the implicit keyword. This keyword is used to modify the operator keyword in the method signature, indicating that the operator will be applied implicitly.


Here’s a simple example:

public readonly record struct Meter
{
    public double Length { get; init; }

    public static implicit operator Meter(double length)
    {
        return new Meter { Length = length };
    }
}

// Usage
Meter meter = 10.0;
Console.WriteLine(meter); // Meter { Length = 10 }


In this example, we’ve defined an implicit operator that allows us to convert a double to a Meter object. This is done without any explicit casting or conversion in the code, hence the term “implicit operator”. The compiler automatically applies the operator when it’s needed, and we don’t need to explicitly cast with (double) or (meter) in front of our variables.


The Power of Implicit Operators

Implicit operators can be incredibly powerful. They allow us to create code that is more expressive and easier to read. By defining implicit operators, we can create types that feel like a natural part of the language, rather than something that’s been bolted on. This can be a popular solution for looking into strongly typed identifiers as well when you want to create types that naturally fit into the domain you’re working in.


For example, consider a Money type. Without implicit operators, working with a Money type might look something like this:

Money m = new Money(10.0m);
decimal amount = m.Amount;


But with implicit operators, we can make this code much more natural and intuitive:

Money m = 10.0m;
decimal amount = m;


This might seem like a small change, but it can make a big difference in the readability of our code. And given that we, as software developers, spend much of our time reading code compared to writing it… Readability should be optimized for!


Practical Applications of Implicit Operators

Implicit operators can be used to improve the readability of your code and to create more intuitive APIs. They are particularly useful when working with domain-specifics or when you want to provide a simple interface for working with complex types. I even used them for creating custom return types that make my exception handling much easier!


For example, you could define implicit operators for a Money type that allows you to work with money values as if they were numbers:

public readonly record struct Money
{
    public decimal Amount { get; init; }

    public static implicit operator Money(decimal amount)
    {
        return new Money { Amount = amount };
    }

    public static implicit operator decimal(Money money)
    {
        return money.Amount;
    }
}

// Usage
Money money = 10.0m;
decimal amount = money;


In this example, we can assign a decimal to a Money object and vice versa, making the Money type feels more like a natural part of the domain you’re working in.


Advanced Usage of Implicit Operators

While implicit operators are powerful, they should be used judiciously. Overuse of implicit operators can lead to code that is difficult to understand and maintain. That sounds counter to what we said earlier, right? Well, the code may be easy to read and this allows us to make assumptions about what is happening. However, if the actual behavior doesn’t line up with our expectations about what we’re reading, suddenly we have a much worse problem than hard-to-read code! It’s also important to remember that implicit operators should not throw exceptions or lose information, which ties into this point exactly.


Here’s an example of a more advanced usage of implicit operators, where we define an implicit operator for a Complex number type:

public readonly record struct Complex
{
    public double Real { get; init; }
    public double Imaginary { get; init; }

    public static implicit operator Complex(double real)
    {
        return new Complex { Real = real };
    }

    public static implicit operator Complex((double real, double imaginary) tuple)
    {
        return new Complex { Real = tuple.real, Imaginary = tuple.imaginary };
    }
}

// Usage
Complex complex1 = 10.0;
Complex complex2 = (10.0, 20.0);


In this example, we can assign a double or a tuple to a Complex object, providing a flexible interface for creating complex numbers. The options for this become seemingly endless when you think about all of the different domains that exist. What about something for working with quantities and doing unit conversions?!


Pitfalls and Things to Avoid

While implicit operators can be incredibly useful, they can also be a source of confusion if not used carefully. One of the main things to avoid is creating implicit operators that can result in data loss or that can throw exceptions. Because implicit operators are applied automatically by the compiler, any exceptions they throw can be difficult to trace.


For example, if we were to have an invalid cast exception thrown because of an improper type conversion operation, we’d be used to seeing a line of code with an explicit cast operation. We’d see in parentheses the type we are trying to cast to being applied to a variable or value and know right away there was some type of incompatibility as suggested by the exception pointing to that line number. With implicit operators, the conversion happens “magically” behind the scenes for us so if there’s any sort of error as a result of the operator executing:


  1. We not only have to pause to recognize there’s some type of implicit operator being applied (i.e. “Wait a second, how is a double value being assigned to this type that is not a double?!”)

  2. We then have to go into that type’s code (let’s hope we have the source for it!) and then go diagnose what’s wrong with the logic.


Another thing to avoid is creating implicit operators that can result in unexpected behavior. For example, if you create an implicit operator that converts a string to an int, it might be unclear whether the conversion is parsing the string as a number or getting the length of the string. Remember from earlier we touched on readability being paramount? Well, this remains true. But if the behavior does not align with what the reader is understanding from the code in front of them, then this will create a lot of confusion.

Conclusion and Best Practices

Congratulations! You’ve just learned about implicit operators in C#, a powerful feature that can make your code more readable and expressive. Remember, while implicit operators are powerful, they should be used with care. Always ensure that your implicit operators do not throw exceptions or lose information. And most importantly, make sure the behavior of your operators is aligned with how the reader of the code would expect things to happen.


Thanks for reading, and consider checking out this article if you want to see how I made a multi-type object for dealing with exceptions using implicit operators! You can also  on the topic that might help solidify some of the concepts. And finally, if you enjoy topics like this and would like a quick weekly summary on software engineering and other C# topics, you should totally check out Dev Leader Weekly! My newsletter is a quick 5-minute read every weekend for you to catch up on some software engineering and C# topics.


Also published here.


The lead image for this article was generated by HackerNoon's AI Image Generator via the prompt "display c# on a laptop’s screen"