using System; public class Test { // This example showcase the following characteristics: // (1) Choice of UoM (Unit of Measure) as Property get/set // (2) Named constructor for choice of UoM // (3) ToString support for UoM // (4) Roundtrip conversion (negligible rounding loss permitted // by double precision) // (5) Operator overloading (but lacking dimensional type check) // // Keep in mind this is just prototype code. public static void Main() { Length len = Length.FromFeet(8.0); Console.WriteLine("feets: " + len.Feet.ToString("F15")); Console.WriteLine("meters: " + len.Meters); Console.WriteLine("tostring: " + len); Console.WriteLine("tostring(ft): " + len.ToString("ft")); Length lenRT = Length.FromMeters(Length.FromFeet(Length.FromMeters(len.Meters).Feet).Meters); Console.WriteLine("roundtrip (m): " + lenRT.ToString("F15")); Console.WriteLine("roundtrip (ft): " + lenRT.ToString("ftF15")); Console.WriteLine("roundtrip (ft): " + lenRT.Feet.ToString("F15")); Dimensioned sum1 = DimensionProduct.FromSum(len, lenRT); Console.WriteLine("sum1: " + sum1); Dimensioned prod1 = DimensionProduct.FromProduct(len, lenRT); Dimensioned prod2 = len * lenRT; Console.WriteLine("product1: " + prod1); Console.WriteLine("product2: " + prod2); } public interface Dimensioned { int[] DimensionIndicator { get; } double Value { get; } } public class DimensionProduct : Dimensioned { private int[] myIndicator = new int[] {0, 0, 0, 0, 0, 0, 0}; private double myValue = 0.0; public int[] DimensionIndicator { get { return (int[]) myIndicator.Clone(); } } public double Value { get { return myValue; } } private DimensionProduct(int[] indicator, double value) { myIndicator = (int[])indicator.Clone(); myValue = value; } public static DimensionProduct FromProduct(Dimensioned a, Dimensioned b) { return new DimensionProduct( _ArrayAdd(a.DimensionIndicator, b.DimensionIndicator), a.Value * b.Value); } public static DimensionProduct FromSum(Dimensioned a, Dimensioned b) { if (!_ArraySame(a.DimensionIndicator, b.DimensionIndicator)) { throw new ArgumentException("Incompatible dimensions", "b"); } return new DimensionProduct(a.DimensionIndicator, a.Value + b.Value); } private static int[] _ArrayAdd(int[] a, int[] b) { int len = Math.Min(a.Length, b.Length); int[] result = new int[len]; for (int k = 0; k < len; ++k) { result[k] = a[k] + b[k]; } return result; } private static bool _ArraySame(int[] a, int[] b) { int len = Math.Min(a.Length, b.Length); for (int k = 0; k < len; ++k) { if (a[k] != b[k]) return false; } return true; } public override string ToString() { // This is a diagnostic implementation only; // we won't know how to properly format the string // for a generic dimension vector. return myValue.ToString("F15") + " " + _ArrayStr(myIndicator); } private static string _ArrayStr(int[] arr) { return "{" + string.Join(",", Array.ConvertAll(arr, x => x.ToString())) + "}"; } } public static class Constants { // note: scaling factors need to be as precise as permitted by // double-precision, to preserve precision in round-trip conversions. public static double FeetToMeter = 0.3048000000; // exact public static double MeterToFeet = 1.0 / 0.3048000000; // nearly exact } public class Length : Dimensioned { public int[] DimensionIndicator { get { return new int[] { 0, 1, 0, 0, 0, 0, 0 }; } } public double Value { get { return myValue; } } private double myValue; public double Meters { get { return myValue; } set { myValue = value; } } public double Feet { get { return myValue * Constants.MeterToFeet; } set { myValue = value * Constants.FeetToMeter; } } public Length() {} // default zero private Length(double initValue) // made private to avoid ambiguity { myValue = initValue; } // unambiguous "named constructors" public static Length FromMeters(double meter) { return new Length(meter); } public static Length FromFeet(double feet) { Length len = new Length(); len.Feet = feet; return len; } public override string ToString() { return ToString(""); } public string ToString(string fmt) { bool useFeet = (fmt != null && fmt.StartsWith( "ft", StringComparison.InvariantCultureIgnoreCase)); if (useFeet) { return Feet.ToString(fmt.Substring(2)) + " feet"; } else { return Meters.ToString(fmt) + " meters"; } } // Try both. The choice depends on whether one has enough time // to create customized classes for each derived physical quantity // (such as the Area class) #if false public static DimensionProduct operator * (Length a, Length b) { return DimensionProduct.FromProduct(a, b); } #else public static Area operator * (Length a, Length b) { return Area.FromSquareMeters(a.Meters * b.Meters); } #endif } public class Area : Dimensioned { public int[] DimensionIndicator { get { return new int[] { 0, 2, 0, 0, 0, 0, 0 }; } } public double Value { get { return myValue; } } private double myValue; public double SquareMeters { get { return myValue; } set { myValue = value; } } public double SquareFeet { get { return myValue * Math.Pow(Constants.MeterToFeet, 2); } set { myValue = value * Math.Pow(Constants.FeetToMeter, 2); } } public static Area FromSquareMeters(double sqm) { Area obj = new Area(); obj.SquareMeters = sqm; return obj; } public static Area FromSquareFeet(double sqft) { Area obj = new Area(); obj.SquareFeet = sqft; return obj; } public override string ToString() // for brevity I did not customize this { return myValue.ToString("F15") + " square meters"; } } }