Let’s say you maintain a class whose constructor expects a configuration object:
class MyDbConnection { public MyDbConnection(MyDbConfiguration config) { … } } ... var config = new MyDbConfiguration { Server = "SuperFastDbServer", User = "jsmith", Password = … }; var connection = new MyDbConnection(config);
Along the way, developers asked for a simple, textual way to set configuration options. To accommodate this, you gave the configuration class a constructor that accepts a settings string as its argument:
var connection = new MyDbConnection(new MyDbConfiguration("server=SuperFastDbServer;user=jsmith;password=…"));
Now, you’ve received a request to further streamline usage by allowing the configuration string to be passed directly to the main class, bypassing the need to reference the settings class:
var connection = new MyDbConnection("server=SuperFastDbServer;user=jsmith;password=…");
Since the goal is to construct instances of the main class by passing a string, it seems the way to implement this request is to give the main class a constructor that accepts a string as its argument.
Surprisingly, adding a constructor isn’t the only way to achieve the desired effect! In fact, it’s possible to satisfy this request without any modifications to the main class. Likely, you’ve already used the functionality that makes this possible—though perhaps without realizing you could use it with classes and structs you create.
However, there is a philosophical question about the appropriateness of applying this technique in this scenario. We’ll touch on this question later. Even if you decide against using the technique in this case, knowing about it hopefully will come in handy down the road when you encounter other, unquestionably appropriate situations where it can be used.
Implicit Conversions
Ever pass an integer as an argument to a method where a decimal is expected? It works, seamlessly. Why? Decimal has an implicit conversion operator that accepts integers and converts them to decimals. When you provided the integer, a call was automatically made to the appropriate conversion operator. You came away happy—you passed in an integer because that’s what you had—and the method came away happy—it received a decimal because that’s what it expected.
Implicit conversion can be used to satisfy the request from the introductory scenario. To do so, define an operator method on the settings class that implicitly converts strings to settings class instances.
class MyDbConfiguration { public MyDbConfiguration() { … } public MyDbConfiguration(string config) { … } public static implicit operator MyDbConfiguration(string config) => new MyDbConfiguration(config); }
With this operator in place, the main class’s single constructor can be called either with a settings object or with a string. When a string is passed, an automatically-inserted call to the conversion operator converts the string to an instance of the expected class.
Thanks to the implicit conversion, class consumers avoid the extra clutter involved with instantiating a settings object while you (the class developer) avoid the need to add a second constructor to the main class.
var connection = new MyDbConnection("server=SuperFastDbServer;user=jsmith;password=…");
Contrasting the Two
With two ways to implement the requested behavior, the question arises, “Which should be preferred?”
The implicit conversion operator approach avoids multiple classes handling the same responsibility (in this case, parsing the configuration string). Overlapping responsibility brings with it complications such as developer confusion (“Do both classes accept the same configuration string syntax?”) and fostering violations of the Don’t Repeat Yourself (DRY) principle.
However, implicit conversion operators are harder to use because they’re not immediately obvious to consuming developers. Looking at a method signature reveals the expected argument types but not what types can be implicitly converted to those expected types. Discovering the latter requires extra digging—a downside avoided by defining the second constructor which explicitly states the argument type (or types) it expects.
Taking into account just the above considerations, which option is better may come down to how comfortable your class consumers are with implicit conversion operators. If they’re familiar with the concept and used to finding them (or have tools/documentation to facilitate their discovery), implicit conversion operators may be the preferable option. Otherwise, adding the second constructor makes things more convenient for users—and so be the more desirable choice.
Philosophical Quandary
However, there is also a philosophical side to this decision. Microsoft’s C# reference suggests “in general” that implicit conversion operators “should never throw exceptions” (ref) to help prevent “the compiler from silently invoking the conversion operation with possibly unforeseen consequences” (ref).
If defining an implicit conversion operator is attractive to you, you’ll need to consider the likelihood that it could raise an exception (probable if parsing text is involved) then determine whether your specific scenario fits into “in general” or is justified in being exceptional (pun intended). For example, do you consider it an unforeseen circumstance if the implicit conversion from configuration string to configuration object instance throws an exception due to an error in the configuration string?
There is a tiny amount of precedence in .Net library code suggesting that perhaps implicit conversion operators may acceptably throw exceptions due to text parsing issues: System.Xml.Linq.XName name = "my:name";
will throw an exception because an invalid character (the colon) is present in the string.
Conversion Operator Options
In closing, let’s overview the other options you have when defining a conversion operator:
- In addition to being defined on the convert-to class (as we did above), conversion operators can also be defined on the convert-from class (i.e. the class which need to be converted to the other type).
- Conversion operators can also be declared as
explicit
. Making an operator explicit requires the presence of an explicit cast in order for the operator to be invoked (e.g.var config = (MyDbConfiguration)"server=..."
). Per the C# Reference, explicit conversion operators are allowed to throw exceptions.
These options weren’t used in this article’s scenario but they may come in handy down the road when you encounter other opportunities to define custom conversion operators.