I'm writing a C# class. As an example, the class is a Distance class to keep track of a distance float
value in meters, while also having multiple properties for different units. Like centimeters, kilometers, etc.
I want to be able to make these properties be implicitly usable in place of numbers (float
) and also as string
s. Meaning, I want to be able to do distance.centimeters + 1
to calculate 1 plus the distance in centimeters units and use it somewhere else, and I also want to be able to do Console.WriteLine(distance.centimeters)
to print a custom string, like appending the unit (e.g. 104 cm
if the meters
property has the value 1.04
).
While researching, I discovered that you can have custom implicit type conversions, but that works on the class/object level, but not the property (which is a float
).
So now I want to create either:
What is a best practice when it comes to this issue? What would be some differences for between these two options that I might need to consider? I'd appreciate all thoughts related to the subject.
Example Code:
public class Distance
{
public float meters;
public Distance (float m)
{
meters = m;
}
public float centimeters
{
get
{
return meters * 100;
}
}
// Option 1
public string centimetersString
{
get
{
return centimeters + " cm";
}
}
// Option 2
public string centimetersToString()
{
return centimeters + " cm";
}
}
You could encapsulate each distance type in its own class, and then use implicit operator
to provide conversions.
A drawback of this approach is that every distance class must contain implicit operator
implementations to convert every other distance type to its type.
With that said, here's an example showing what I mean:
using System;
namespace Demo
{
static class Program
{
static void Main()
{
Metres metres = 10;
Centimetres centimetres = metres;
Console.WriteLine(centimetres); // Prints "1000cm"
metres = centimetres;
Console.WriteLine(metres); // Prints "10m"
var kilometres = (Kilometres) centimetres;
Console.WriteLine(kilometres); // Prints "0.01km"
}
}
public sealed class Metres
{
public Metres(float metres)
{
_metres = metres;
}
public float Distance => _metres;
public static implicit operator Metres(Centimetres centimetres) => new (centimetres.Distance / 100.0f);
public static implicit operator Metres(Kilometres kilometres) => new (kilometres.Distance / 100_000.0f);
public static implicit operator Metres(float metres) => new (metres);
public override string ToString()
{
return $"{_metres}m";
}
readonly float _metres;
}
public sealed class Centimetres
{
public Centimetres(float centimetres)
{
_centimetres = centimetres;
}
public float Distance => _centimetres;
public static implicit operator Centimetres(Metres metres) => new (metres.Distance * 100.0f);
public static implicit operator Centimetres(Kilometres kilometres) => new (kilometres.Distance * 100_000.0f);
public static implicit operator Centimetres(float centimetres) => new (centimetres);
public override string ToString()
{
return $"{_centimetres}cm";
}
readonly float _centimetres;
}
public sealed class Kilometres
{
public Kilometres(float kilometres)
{
_kilometres = kilometres;
}
public float Distance => _kilometres;
public static implicit operator Kilometres(Metres metres) => new (metres.Distance / 1000.0f);
public static implicit operator Kilometres(Centimetres centimetres) => new (centimetres.Distance /100_000.0f);
public static implicit operator Kilometres(float kilometres) => new (kilometres);
public override string ToString()
{
return $"{_kilometres}km";
}
readonly float _kilometres;
}
}
Runnable example on .net Fiddle: https://dotnetfiddle.net/tJE62S
If you want to add some overloaded arithmetic operators it gets a little more involved:
using System;
namespace Demo
{
static class Program
{
static void Main()
{
Metres metres = 10;
Centimetres centimetres = metres;
Console.WriteLine(centimetres); // Prints "1000cm"
metres = centimetres;
Console.WriteLine(metres); // Prints "10m"
var kilometres = (Kilometres) centimetres;
Console.WriteLine(kilometres); // Prints "0.01km"
var addedMetres = metres + 10;
Console.WriteLine(addedMetres); // Prints "20m"
var subtractedCm = centimetres - 100;
Console.WriteLine(subtractedCm); // Prints "900cm"
// This would be an ambiguous call - should the result be Centimetres or Metres?
// var difference = addedMetres - subtractedCm;
// So fix it by casting one of the operands to the result type that you want:
var diffCm = (Centimetres)addedMetres - subtractedCm;
Console.WriteLine(diffCm); // Prints "1100cm"
var diffM = addedMetres - (Metres)subtractedCm;
Console.WriteLine(diffM); // Prints "11m"
}
}
public sealed class Metres
{
public Metres(float metres)
{
_metres = metres;
}
public float Distance => _metres;
public static implicit operator Metres(Centimetres centimetres) => new (centimetres.Distance / 100.0f);
public static implicit operator Metres(Kilometres kilometres) => new (kilometres.Distance / 100_000.0f);
public static implicit operator Metres(float metres) => new (metres);
public static Metres operator +(Metres a, Metres b) => new (a.Distance + b.Distance);
public static Metres operator -(Metres a, Metres b) => new (a.Distance - b.Distance);
public override string ToString()
{
return $"{_metres}m";
}
readonly float _metres;
}
public sealed class Centimetres
{
public Centimetres(float centimetres)
{
_centimetres = centimetres;
}
public float Distance => _centimetres;
public static implicit operator Centimetres(Metres metres) => new (metres.Distance * 100.0f);
public static implicit operator Centimetres(Kilometres kilometres) => new (kilometres.Distance * 100_000.0f);
public static implicit operator Centimetres(float centimetres) => new (centimetres);
public static Centimetres operator +(Centimetres a, Centimetres b) => new (a.Distance + b.Distance);
public static Centimetres operator -(Centimetres a, Centimetres b) => new (a.Distance - b.Distance);
public override string ToString()
{
return $"{_centimetres}cm";
}
readonly float _centimetres;
}
public sealed class Kilometres
{
public Kilometres(float kilometres)
{
_kilometres = kilometres;
}
public float Distance => _kilometres;
public static implicit operator Kilometres(Metres metres) => new (metres.Distance / 1000.0f);
public static implicit operator Kilometres(Centimetres centimetres) => new (centimetres.Distance /100_000.0f);
public static implicit operator Kilometres(float kilometres) => new (kilometres);
public static Kilometres operator +(Kilometres a, Kilometres b) => new (a.Distance + b.Distance);
public static Kilometres operator -(Kilometres a, Kilometres b) => new (a.Distance - b.Distance);
public override string ToString()
{
return $"{_kilometres}km";
}
readonly float _kilometres;
}
}
Runnable example on .net Fiddle: https://dotnetfiddle.net/uoN1Wr