There is no general answer. I would use it carefully, and only if keeps the code understandable and straight-forward (i.e. it is showing expected behavior).
So I can give you an answer based on a practical example, if you follow it along you will understand when to use and when better not to use conversion operators:
I recently wanted to have a simpler way to deal with Guids. My design goals were: Simplify Syntax and initialization, simplify conversions and variable assignments.
As you know, if you need to create GUIDs the usage is a bit cumbersome:
Example 1:
Out of the box:
var guids = new Guid[] {
new Guid("2f78c861-e0c3-4d83-a2d2-cac269fb87f1"), new Guid("2f78c861-e0c3-4d83-a2d2-cac269fb87f2"),
new Guid("2f78c861-e0c3-4d83-a2d2-cac269fb87f3")
};
What if you just could implicitly convert the GUID string to a string, like:
var guids = new EasyGuid[] {
"2f78c861-e0c3-4d83-a2d2-cac269fb87f1", "2f78c861-e0c3-4d83-a2d2-cac269fb87f2",
"2f78c861-e0c3-4d83-a2d2-cac269fb87f3"
};
That would allow to paste a list of GUIDs directly from a JSON file into C# code.
Example 2:
To initialize an array, you need to do the following:
var guids = new Guid[30];
for (int i = 0; i < 30; i++)
{
guids[i] = System.Guid.Empty; // Guid with 000...
}
Wouldn't it be easier to just Guids like:
var guids = new EasyGuid[30]; // create array with 30 Guids (value null)
The guids from both examples could then be used like
foreach (Guid g in guids)
{
g.Dump();
}
In other words, they can be implicitly converted into a "normal" Guid just when they need to be used. And in the 2nd example, if they are null, an Empty Guid is implicitly assigned on the fly.
How can you do that? You can't inherit from System.Guid. But you can use implicit conversions. Take a look at this class, I called it EasyGuid
, it makes the declarations above possible:
/// <summary>
/// Easy GUID creation
/// Written by Matt, 2020
/// </summary>
public class EasyGuid
{
// in case you want to replace GUID generation
// by RT.Comb, call Provider.PostgreSql.Create()
private static System.Guid NewGuid => System.Guid.NewGuid();
private System.Guid _guid = EasyGuid.NewGuid;
public EasyGuid()
{
_guid = NewGuid;
}
public EasyGuid(string s)
{
_guid = new System.Guid(s); // convert string to Guid
}
// converts string to Guid
public static implicit operator EasyGuid(string s) => new EasyGuid(s);
// converts EasyGuid to Guid, create empty guid (Guid with 0) if null
public static implicit operator System.Guid(EasyGuid g)
=> (g == null) ? System.Guid.Empty : g.ToGuid();
// converts EasyGuid to Guid?, null will be passed through
public static implicit operator System.Guid?(EasyGuid g)
=> (g == null) ? null : (Guid?)g.ToGuid();
public override string ToString() => _guid.ToString();
public System.Guid ToGuid() => _guid;
}
You can see that EasyGuid
can implicitly convert a string into an EasyGuid and can convert an EasyGuid into a Guid - either implicitly or explicitly by calling ToGuid()
. Also it can be printed as string because I have overridden .ToString()
.
Finally, I wanted to be able to easily generate new GUIDs on the fly. I achieved this by writing.
// converts EasyGuid to Guid, create empty guid (Guid with 0) if null
public static implicit operator System.Guid(EasyGuid g)
=> (g == null) ? EasyGuid.NewGuid : g.ToGuid();
Which would have the effect that
var guids = new EasyGuid[30];
would generate new GUIDs on the fly as soon as they are converted to GUID. But I got the feedback from @OskarBerggren that this approach - while it is easy to implement, would cause confusion - the code wouldn't be obvious any more for others reading it (thank you, Oskar for this hint!). It could also lead to unexpected issues (bugs). Remember what Microsoft said:
Do not provide a conversion operator if such conversion is not clearly expected by the end users.
So instead I implemented this not via implicit conversions, but with extension methods as follows:
public static class Extensions
{
public static System.Guid[] ToGuids(this EasyGuid[] guidArray, bool replaceNullByNewGuid = false)
=> guidArray.ToList().ToGuids(replaceNullByNewGuid).ToArray();
public static List<System.Guid> ToGuids(this List<EasyGuid> easyGuidList, bool replaceNullByNewGuid = false)
{
var guidList = new List<Guid>();
foreach (var g in easyGuidList)
{
Guid result = (g!=null) ? g : ((replaceNullByNewGuid) ? new EasyGuid().ToGuid() : System.Guid.Empty);
guidList.Add(result);
}
return guidList;
}
}
This is more straightforward, since now you have the choice:
// shorter: .ToGuids(true)
var guids = new EasyGuid[30].ToGuids(replaceNullByNewGuid: true);
And if you just want to create an array with empty Guids:
var guids = new EasyGuid[30].ToGuids();
does it (same for list of GUIDS).
This example is showing that implict conversion operators can easily cause confusion by unexpected behavior. Sometimes, it is better to use extension methods (as shown here).
I think this example shows that there are occasions where the conversion operators can make your life easier, and others where you should stop and think and find more obvious ways of implementation.
For completeness: Other cases are:
var eg1 = new EasyGuid(); // simple case: new Guid
Guid g = eg1; g.Dump(); // straight-forward conversion
EasyGuid eg2 = null; // null-handling
Guid g2 = eg2; g2.Dump(); // converted to 00000000-0000-0000-0000-000000000000
Guid? g3 = eg2; g3.Dump(); // will be null
Degrees
andRadians
classes to manage angles. They have implicit conversions to/from each other andDouble
and have immensely simplified our usage of angles and all but eliminated cases of accidentally using degrees instead of radians (and vice-versa) in various trigonometric functions. – Hurd