Abdulrahman Alhemeiri
Abdulrahman Alhemeiri

Reputation: 675

What's a C# best practice for having a property be easily cast to multiple types?

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 strings. 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:

  1. a string property different from the float property (that has "String" at the end of its name)
  2. Or a method that returns a string (that has "ToString()" at the end of its name to be similar to the overridden ".ToString()" method).

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";
    }
}

Upvotes: 1

Views: 204

Answers (4)

Abdulrahman Alhemeiri
Abdulrahman Alhemeiri

Reputation: 675

After reading all answers, I chose to accept Matthew Watson's answer because it is the closest thing to what I want to do. Here's what my code sort of looks like inspired by his answer. If I need an additional metric prefix, I would just add To<InsertPrefix>() to the interface, implement it in each class, and create a class for it and implement the methods accordingly.

public interface IDistance
{
    public double Value();
    public string ToString();
    public Centimeters ToCentimeters();
    public Meters ToMeters();
}

public class Meters : IDistance
{
    private double _meters;

    public Meters(double m)
    {
        _meters = m;
    }

    public Meters(IDistance distance)
    {
        _meters = distance.ToMeters()._meters;
    }

    public double Value()
    {
        return _meters;
    }

    public override string ToString() => $"{_meters} m";

    public Centimeters ToCentimeters() => new Centimeters(_meters * 100);

    public Meters ToMeters() => this;

    public static Meters operator +(Meters m, IDistance d) => new Meters(m.Value() + d.ToMeters().Value());
    public static Meters operator +(IDistance d, Meters m) => new Meters(m.Value() + d.ToMeters().Value());


}

public class Centimeters : IDistance
{
    private double _centimeters;

    public Centimeters(double c)
    {
        _centimeters = c;
    }

    public Centimeters(IDistance distance)
    {
        _centimeters = distance.ToCentimeters()._centimeters;
    }

    public double Value()
    {
        return _centimeters;
    }

    public override string ToString() => $"{_centimeters} cm";

    public Centimeters ToCentimeters() => this;

    public Meters ToMeters() => new Meters(_centimeters / 100);

    public static Centimeters operator +(Centimeters c, IDistance d) => new Centimeters(c.Value() + d.ToCentimeters().Value());
    public static Centimeters operator +(IDistance d, Centimeters c) => new Centimeters(c.Value() + d.ToCentimeters().Value());
}

Usage

public void Test()
{
    IDistance distance = new Centimeters(4);
    distance = distance + new Meters(4);
    Trace.WriteLine(distance.ToMeters().ToString()); // "1.04 m"
}

Upvotes: 2

Matthew Watson
Matthew Watson

Reputation: 109802

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

Upvotes: 1

patawa91
patawa91

Reputation: 46

You could break them up into different classes. One for each measurement type. This way you have a class dedicated to a particular type (say centimeters), and you could have different properties within that class to return your value formatted in multiple formats. Principle is you may be trying to do too much in one class. Here is some sample code. There could be multiple variations on this such as abstract base class etc. This is "A" solution to show you what I mean by splitting into multiple classes.

Code is from LinqPad.

void Main()
{
    var dc = new Distance(10);
    
    dc.ToString().Dump();
    var cent = dc.ToCentimeters();
    cent.Centimeters.Dump();
    cent.ToString().Dump();
    
    var mil = dc.ToMillimeters();
    mil.Millimeters.Dump();
    mil.ToString().Dump();
    
    // outputs
    //10 Meters
    //1000
    //1000 Centimeters
    //10000
    //10000 Millimeters
}

// You can define other methods, fields, classes and namespaces here
public class Distance
{
    private float _meters;
    public Distance (float meters)
    {
        _meters = meters;
    }
    
    public DistanceCentimeters ToCentimeters(){
        return new DistanceCentimeters(_meters * 100);
    }
    
    public DistanceMillimeters ToMillimeters(){
        return new DistanceMillimeters(_meters * 1000);
    }
    
    public override string ToString(){
        return $"{_meters} Meters";
    }
}

public class DistanceCentimeters{
    public DistanceCentimeters(float centimeters){
        _centimeters = centimeters;
    }

    private float _centimeters;
    public float Centimeters
    {
        get
        {
            return _centimeters;
        }
    }
    
    public override string ToString(){
        return $"{Centimeters} Centimeters";
    }
}

public class DistanceMillimeters {
    public DistanceMillimeters(float millimeters){
        _millimeters = millimeters;
    }
    
    private float _millimeters;
    public float Millimeters
    {
        get
        {
            return _millimeters;
        }
    }
    
    public override string ToString(){
        return $"{Millimeters} Millimeters";
    }
}

Upvotes: 1

Alp
Alp

Reputation: 396

I made something for you, i hope it helps and what you needs. The default value is always cm. But you can change it of course as you want.

public class Distance
{
    public float Value { get; set; }
    public Distance(float value, MetricSystem metric)
    {
        Value = metric switch
        {
            MetricSystem.Centimeter => value,
            MetricSystem.Meter => value * 100,
            _ => Value
        };
    }

    public void AddValue(float value, MetricSystem metric)
    {
        Value += metric switch
        {
            MetricSystem.Centimeter => value,
            MetricSystem.Meter => value * 100,
            _ => 0
        };
    }

    public string ReadValue(MetricSystem metric)
    {
        return metric switch
        {
            MetricSystem.Centimeter => $"{Value} cm",
            MetricSystem.Meter => $"{Value / 100} m",
            _ => string.Empty
        };
    }
}

public enum MetricSystem
{
    Centimeter,
    Meter
}

And the result for me: enter image description here

Upvotes: 1

Related Questions