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