C# DataMeasurement object for comparing, converting, and adding or substracting data sizes

I created a C# DataMeasurement object a while ago but recently really started to use it. I wrote unit tests for it and fixed comparison and add/substract of nulls and wrote Unit tests for it.

The intent of the object is to be able to use any Data size and do the following:

  1. Convert it to any other data size type, 1 GB converts to 1024 MB.
  2. Compare it to any other data size type, 1 GB = 1024 MB.
  3. Add any other data size type and have it add correctly, 1 GB + 1024 MB = 2GB.

So you can easily compare things like 12343 bytes.

You can create the object in a lot of different ways as I have multiple constructors, and it is easy to add your own constructor.

Here is the object:

using System;
using System.Collections.Generic;
using System.Text;

namespace LANDesk.Install.Common
{
    public class DataMeasurement
    {
        #region Member Variables
        // This is the size in the current type. For example, 1 GB = 1.0. But
        // if you convert the Type to Megabyte, the value should change to 1024.0;
        protected Double _DataSize;
        protected DataType _DataType;
        #endregion

        #region Constructors

        /// <summary>
        /// The default constructor.  This initializes and object with a
        /// DataSize of 0 and a DataType of Byte.
        /// </summary>
        public DataMeasurement()
        {
            _DataSize = 0;
            _DataType = DataType.Byte;
        }

        /// <summary>
        /// This constructor takes a value and assumes the data measurement
        /// type is in bytes.
        /// </summary>
        /// <param name="inValue">The measurement value as a ulong.</param>
        public DataMeasurement(UInt64 inSizeInBytes)
        {
            _DataType = DataType.Byte;
            _DataSize = inSizeInBytes * 8;
        }

        /// <summary>
        /// This constructor takes a value and a data measurement type.
        /// </summary>
        /// <param name="inValue">The measurement value as a ulong.</param>
        /// <param name="inType">The measurement type.</param>
        public DataMeasurement(UInt64 inValue, DataType inType)
        {
            _DataType = inType;
            _DataSize = inValue;
        }

        /// <summary>
        /// This constructor takes a double value and defaults to megabyte.
        /// </summary>
        /// <param name="inValue"></param>
        public DataMeasurement(double inValue)
        {
            _DataType = DataType.Megabyte;
            _DataSize = inValue;
        }

        /// <summary>
        /// This constructor takes a value and a data measurement type.
        /// </summary>
        /// <param name="inValue">The measurement value as a double.</param>
        /// <param name="inType">The measurement type.</param>
        public DataMeasurement(double inValue, DataType inType)
        {
            _DataType = inType;
            _DataSize = inValue;
        }

        /// <summary>
        /// This constructor takes a value and a data measurement type.
        /// </summary>
        /// <param name="inValue">The measurement value as a double.</param>
        /// <param name="inType">The measurement type as a string, "GB", "Gigabyte".</param>
        public DataMeasurement(UInt64 inValue, String inType)
        {
            _DataSize = inValue;
            GetDataTypeFromString(inType);
        }

        /// <summary>
        /// This constructor takes a string representation of a data measurement.
        /// </summary>
        /// <param name="inValueAndType">The measurement value and type in a
        /// single string such as "49 GB" or "49 GBSI" or "49 Gigabyte". There
        /// must be a space as it is used to split the value from the type, so
        ///  "49GB" is invalid because there is no space delimeter.</param>
        public DataMeasurement(String inValueAndType)
        {
            char[] split = { ' ' };
            String[] data = inValueAndType.Split(split);
            _DataSize = Convert.ToUInt64(data[0]);
            GetDataTypeFromString(data[1]);
        }
        #endregion

        #region Properties
        public Double Size
        {
            get { return _DataSize; }
            set { _DataSize = value; }
        }

        public DataType DataSizeType
        {
            get { return _DataType; }
            set { _DataType = value; }
        }

        public ShortName DataSizeTypeShortName
        {
            get { return (ShortName)_DataType; }
            set { _DataType = (DataType)value; }
        }

        public ulong Bit
        {
            get { return (ulong)ConvertToType(DataType.Bit, false); }
            set
            {
                _DataType = DataType.Bit;
                _DataSize = value;
            }
        }

        public Double Byte
        {
            get { return ConvertToType(DataType.Byte, false); }
            set
            {
                _DataType = DataType.Byte;
                _DataSize = value;
            }
        }

        public Double KilobitSI
        {
            get { return ConvertToType(DataType.KilobitSI, false); }
            set
            {
                _DataType = DataType.KilobitSI;
                _DataSize = value;
            }
        }

        public Double Kilobit
        {
            get { return ConvertToType(DataType.Kilobit, false); }
            set
            {
                _DataType = DataType.Kilobit;
                _DataSize = value;
            }
        }
        public Double KilobyteSI
        {
            get { return ConvertToType(DataType.KilobyteSI, false); }
            set
            {
                _DataType = DataType.KilobyteSI;
                _DataSize = value;
            }
        }

        public Double Kilobyte
        {
            get { return ConvertToType(DataType.Kilobyte, false); }
            set
            {
                _DataType = DataType.Kilobit;
                _DataSize = value;
            }
        }

        public Double MegabitSI
        {
            get { return ConvertToType(DataType.MegabitSI, false); }
            set
            {
                _DataType = DataType.MegabitSI;
                _DataSize = value;
            }
        }

        public Double Megabit
        {
            get { return ConvertToType(DataType.Megabit, false); }
            set
            {
                _DataType = DataType.Megabit;
                _DataSize = value;
            }
        }
        public Double MegabyteSI
        {
            get { return ConvertToType(DataType.MegabyteSI, false); }
            set
            {
                _DataType = DataType.MegabyteSI;
                _DataSize = value;
            }
        }

        public Double Megabyte
        {
            get { return ConvertToType(DataType.Megabyte, false); }
            set
            {
                _DataType = DataType.Megabyte;
                _DataSize = value;
            }
        }

        public Double GigabitSI
        {
            get { return ConvertToType(DataType.GigabitSI, false); }
            set
            {
                _DataType = DataType.GigabitSI;
                _DataSize = value;
            }
        }

        public Double Gigabit
        {
            get { return ConvertToType(DataType.Gigabit, false); }
            set
            {
                _DataType = DataType.Gigabit;
                _DataSize = value;
            }
        }
        public Double GigabyteSI
        {
            get { return ConvertToType(DataType.GigabyteSI, false); }
            set
            {
                _DataType = DataType.GigabyteSI;
                _DataSize = value;
            }
        }

        public Double Gigabyte
        {
            get { return ConvertToType(DataType.Gigabyte, false); }
            set
            {
                _DataType = DataType.Gigabyte;
                _DataSize = value;
            }
        }

        public Double TerabitSI
        {
            get { return ConvertToType(DataType.TerabitSI, false); }
            set
            {
                _DataType = DataType.TerabitSI;
                _DataSize = value;
            }
        }

        public Double Terabit
        {
            get { return ConvertToType(DataType.Terabit, false); }
            set
            {
                _DataType = DataType.Terabit;
                _DataSize = value;
            }
        }
        public Double TerabyteSI
        {
            get { return ConvertToType(DataType.TerabyteSI, false); }
            set
            {
                _DataType = DataType.TerabyteSI;
                _DataSize = value;
            }
        }

        public Double Terabyte
        {
            get { return ConvertToType(DataType.Terabyte, false); }
            set
            {
                _DataType = DataType.Terabyte;
                _DataSize = value;
            }
        }

        public Double PetabitSI
        {
            get { return ConvertToType(DataType.PetabitSI, false); }
            set
            {
                _DataType = DataType.PetabitSI;
                _DataSize = value;
            }
        }

        public Double Petabit
        {
            get { return ConvertToType(DataType.Petabit, false); }
            set
            {
                _DataType = DataType.Petabit;
                _DataSize = value;
            }
        }
        public Double PetabyteSI
        {
            get { return ConvertToType(DataType.PetabyteSI, false); }
            set
            {
                _DataType = DataType.PetabyteSI;
                _DataSize = value;
            }
        }

        public Double Petabyte
        {
            get { return ConvertToType(DataType.Petabyte, false); }
            set
            {
                _DataType = DataType.Petabyte;
                _DataSize = value;
            }
        }

        public Double ExabitSI
        {
            get { return ConvertToType(DataType.ExabitSI, false); }
            set
            {
                _DataType = DataType.ExabitSI;
                _DataSize = value;
            }
        }

        public Double Exabit
        {
            get { return ConvertToType(DataType.Exabit, false); }
            set
            {
                _DataType = DataType.Exabit;
                _DataSize = value;
            }
        }

        public Double ExabyteSI
        {
            get { return ConvertToType(DataType.ExabyteSI, false); }
            set
            {
                _DataType = DataType.ExabyteSI;
                _DataSize = value;
            }
        }

        public Double Exabyte
        {
            get { return ConvertToType(DataType.Exabyte, false); }
            set
            {
                _DataType = DataType.Exabyte;
                _DataSize = value;
            }
        }
        #endregion

        #region Functions
        /// <summary>
        /// Writes the DataMeasurement to string, such as "1 GB" or "1024 KB".
        /// It always uses the DataSize and DataType values.
        /// </summary>
        override public string ToString()
        {
            return String.Format("{0} {1}", _DataSize, ((ShortName)_DataType).ToString());
        }

        /// <summary>
        /// Writes the DataMeasurement to string, such as "1 GB" or "1024 KB".
        /// However, it does so in the DataType that you specify.
        /// </summary>
        /// <param name="inDataType">The type to output to string. For example
        /// If the measurement is "4 Gigabyte" but you pass in Megabyte, it
        /// will output "4096 MB".</param>
        /// <returns></returns>
        public string ToString(DataType inDataType)
        {
            Double size = ConvertToType(inDataType, false);
            return String.Format("{0} {1}", size, ((ShortName)inDataType).ToString());
        }

        public override int GetHashCode()
        {
            return base.GetHashCode();
        }

        public override bool Equals(object obj)
        {
            if (obj == null)
                return false;

            DataMeasurement right = obj as DataMeasurement;
            if ((object)right == null)
                return false;

            return this == right;
        }

        private void GetDataTypeFromString(String inType)
        {
            if (inType.Length == 2 || inType.Length == 4)
            {   // Short name was used so get the ShortName and case it to a DataType
                // KB and Kb are not the same, so case is Important
                _DataType = (DataType)Enum.Parse(typeof(ShortName), inType, false);
            }
            else
            {   // Long name was used so get
                // We can ignore case because Kilobit and Kilobyte are different.
                _DataType = (DataType)Enum.Parse(typeof(DataType), inType, true);
            }
        }

        /// <summary>
        /// This function converts the stored measurement from its current
        /// value to a new value using the new DataType.
        /// </summary>
        /// <param name="inNewType">The new DataType. For example If the
        /// measurement is "4 Gigabyte" but you pass in Megabyte, it will
        /// change the value to "4096 Megabyte".</param>
        /// <returns>The new DataSize for the new type is returned.</returns>
        public Double ConvertToType(DataType inNewType)
        {
            return ConvertToType(inNewType, true);
        }

        /// <summary>
        /// This function converts the stored measurement from its current
        /// value to a new value using the new DataType.
        /// </summary>
        /// <param name="inNewType">The new DataType.. For example If the
        /// measurement is "4 Gigabyte" but you pass in Megabyte, it will
        /// change the value to "4096 Megabyte".</param>
        /// <param name="inChangeObjectMeasurementType">A bool value that
        /// specifies whether to change the whole object.</param>
        /// <returns>The new DataSize for the new type is returned.</returns>
        public Double ConvertToType(DataType inNewType, bool inChangeObjectMeasurementType)
        {
            double ret = 0;
            bool isSet = false;
            if (inNewType == _DataType)
            {
                ret = _DataSize;
                isSet = true;
            }
            else if (inNewType == DataType.Bit)
            {
                ret = (ulong)_DataType * _DataSize * 8;
                isSet = true;
            }
            else if (_DataType == DataType.Bit)
            {
                ret = _DataSize / 8 / (ulong)inNewType;
                isSet = true;
            }

            if (!isSet)
                ret = (ulong)_DataType * _DataSize / (ulong)inNewType;

            if (inChangeObjectMeasurementType)
            {
                _DataType = inNewType;
                Size = ret;
            }

            return ret;
        }
        #endregion

        #region Operator Override Functions
        public static bool operator ==(DataMeasurement left, DataMeasurement right)
        {
            if (null == (object)left && null == (object)right)
                return true;
            if (null == (object)right || null == (object)left)
                return false;

            if (left.DataSizeType == right.DataSizeType)
            {
                return left.Size == right.Size;
            }
            else
            {
                return left.Byte == right.Byte;
            }
        }

        public static bool operator !=(DataMeasurement left, DataMeasurement right)
        {
            if (null == (object)left && null == (object)right)
                return false;
            if (null == (object)right || null == (object)left)
                return true;

            if (left.DataSizeType == right.DataSizeType)
            {
                return left.Size != right.Size;
            }
            else
            {
                return left.Byte != right.Byte;
            }
        }

        public static bool operator >(DataMeasurement left, DataMeasurement right)
        {
            if (null == (object)left && null == (object)right)
                return false;
            if (null == (object)right)
                return true;
            if (null == (object)left)
                return false;

            if (left.DataSizeType == right.DataSizeType)
            {
                return left.Size > right.Size;
            }
            else
            {
                return left.Byte > right.Byte;
            }
        }

        public static bool operator >=(DataMeasurement left, DataMeasurement right)
        {
            if (left.DataSizeType == right.DataSizeType)
            {
                return left.Size >= right.Size;
            }
            else
            {
                return left.Byte >= right.Byte;
            }
        }

        public static bool operator <(DataMeasurement left, DataMeasurement right)
        {
            if (null == (object)left && null == (object)right)
                return false;
            if (null == (object)right)
                return false;
            if (null == (object)left)
                return true;
            if (left.DataSizeType == right.DataSizeType)
            {
                return left.Size < right.Size;
            }
            else
            {
                return left.Byte < right.Byte;
            }
        }

        public static bool operator <=(DataMeasurement left, DataMeasurement right)
        {
            if (left.DataSizeType == right.DataSizeType)
            {
                return left.Size <= right.Size;
            }
            else
            {
                return left.Byte <= right.Byte;
            }
        }

        public static DataMeasurement operator +(DataMeasurement left, DataMeasurement right)
        {
            return left + right.ConvertToType(left.DataSizeType, false);
        }

        public static DataMeasurement operator +(DataMeasurement left, double right)
        {
            if ((null == (object)left && null == (object)right) || null == (object)right)
                return left;
            if (null == (object)left)
                return new DataMeasurement(right);
            return new DataMeasurement(left.Size + right, left.DataSizeType);
        }

        public static DataMeasurement operator -(DataMeasurement left, DataMeasurement right)
        {
            return left - right.ConvertToType(left.DataSizeType, false);
        }

        public static DataMeasurement operator -(DataMeasurement left, double right)
        {
            if ((null == (object)left && null == (object)right) || null == (object)right)
                return left;
            if (null == (object)left)
                return new DataMeasurement(right);
            return new DataMeasurement(left.Size - right, left.DataSizeType);
        }
        #endregion

        #region Enums
        public enum ShortName : ulong
        {
            b = 0, // Bit must be handled special
            // Everything after is in bytes
            B = 1,
            KbSI = 125,
            Kb = 128,
            KBSI = 1000,
            KB = 1024,
            MbSI = 125000,
            Mb = 131072,
            MBSI = 1000000,
            MB = 1048576,
            GbSI = 125000000,
            Gb = 134217728,
            GBSI = 1000000000,
            GB = 1073741824,
            TbSI = 125000000000,
            Tb = 137438953472,
            TBSI = 1000000000000,
            TB = 1099511627776,
            PbSI = 125000000000000,
            Pb = 140737488355328,
            PBSI = 1000000000000000,
            PB = 1125899906842624,
            EbSI = 125000000000000000,
            Eb = 144115188075855872,
            EBSI = 1000000000000000000,
            EB = 1152921504606846976
        }

        public enum DataType : ulong
        {
            Bit = 0, // Bit must be handled special
            // Everything after is in bytes
            Byte = 1,
            KilobitSI = 125,
            Kilobit = 128,
            KilobyteSI = 1000,
            Kilobyte = 1024,
            MegabitSI = 125000,
            Megabit = 131072,
            MegabyteSI = 1000000,
            Megabyte = 1048576,
            GigabitSI = 125000000,
            Gigabit = 134217728,
            GigabyteSI = 1000000000,
            Gigabyte = 1073741824,
            TerabitSI = 125000000000,
            Terabit = 137438953472,
            TerabyteSI = 1000000000000,
            Terabyte = 1099511627776,
            PetabitSI = 125000000000000,
            Petabit = 140737488355328,
            PetabyteSI = 1000000000000000,
            Petabyte = 1125899906842624,
            ExabitSI = 125000000000000000,
            Exabit = 144115188075855872,
            ExabyteSI = 1000000000000000000,
            Exabyte = 1152921504606846976
        }
        #endregion
    }
}

I could probably do a better job of testing this. Here is a test class that succeeds. If you want more testing add it.

Hopefully this helps you out.

using LANDesk.Install.Common;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System;
using System.Collections.Generic;

namespace LANDesk.Install.Common.Tests
{
    /// <summary>
    ///This is a test class for DataMeasurementTest and is intended
    ///to contain all DataMeasurementTest Unit Tests
    ///</summary>
    [TestClass()]
    public class DataMeasurementTest
    {
        List<DataMeasurement> sizes = new List<DataMeasurement>();

        [TestMethod()]
        public void DataMeasurementTestAdditionAndGreaterThanLessThan()
        {
            DataMeasurementEqualityTest();
            DataMeasurement OneGB = new DataMeasurement("1 GB");
            DataMeasurement OneGBinMB = new DataMeasurement("1024 MB");
            DataMeasurement twoGB = new DataMeasurement(2, DataMeasurement.DataType.Gigabyte);
            DataMeasurement twoGBadded = OneGB + OneGBinMB;
            TestEqualValues(twoGB, twoGBadded);

            foreach (DataMeasurement dm in sizes)
            {
                TestLeftIsGreater(twoGB, dm);
                TestRightIsGreater(dm, twoGB);
            }
        }

        [TestMethod()]
        public void DataMeasurementTestSubtractionAndGreaterThanLessThan()
        {
            DataMeasurementEqualityTest();
            DataMeasurement tenGB = new DataMeasurement(10, DataMeasurement.DataType.Gigabyte);
            DataMeasurement NineGB = new DataMeasurement(9, DataMeasurement.DataType.Gigabyte);
            DataMeasurement NineGBbySubtraction = tenGB - sizes[0];
            TestEqualValues(NineGB, NineGBbySubtraction);
            TestLeftIsGreater(tenGB, NineGB);
            TestLeftIsGreater(tenGB, NineGBbySubtraction);
            TestRightIsGreater(NineGB, tenGB);
            TestRightIsGreater(NineGBbySubtraction, tenGB);
        }

        [TestMethod()]
        public void TestConversion()
        {
            DataMeasurement dm = new DataMeasurement("4 GB");
            dm.ConvertToType(DataMeasurement.DataType.Megabyte);
            Assert.IsTrue(dm.Size == (double)4096);
            Assert.IsTrue(dm.ToString() == "4096 MB");
        }

        [TestMethod()]
        public void DataMeasurementOtherTest()
        {
            sizes = new List<DataMeasurement>();
            int i = -1;

            // Test 1 - Check that conversion is working
            i++;
            sizes.Add(new DataMeasurement("1024 bit"));
            Assert.IsTrue(sizes[i].Bit == 1024, "Test 1, to Bit.");
            Assert.IsTrue(sizes[i].Byte == 1024 / 8, "Test 1, to Byte.");
            double expected = 1024 / 8 / 1024.0;
            Assert.IsTrue(sizes[i].Kilobyte == expected, "Test 1, " + sizes[i].Kilobyte + " should equal " + expected);

            // Test 2 - Check that conversion is working
            i++;
            sizes.Add(new DataMeasurement(4, DataMeasurement.DataType.Gigabyte));
            Assert.IsTrue(sizes[i].Bit == 34359738368, "Test 2, to bit.");
            Assert.IsTrue(sizes[i].Byte == 4294967296, "Test 2, to Byte.");
            Assert.IsTrue(sizes[i].Kilobyte == 4 * 1024 * 1024, "Test 2, to Kilobyte.");
            expected = 4.0 / 1024;
            Assert.IsTrue(sizes[i].Terabyte == expected, "Test 2, " + sizes[i].Terabyte + " should equal " + expected);

            // Test 3 - Comparisons
            Assert.IsTrue(sizes[i - 1] < sizes[i], "Test 3, " + sizes[i - 1] + " is less than " + sizes[i]);
            Assert.IsTrue(sizes[i - 1] <= sizes[i], "Test 3, " + sizes[i - 1] + " is less than or equal to " + sizes[i]);
            Assert.IsFalse(sizes[i - 1] > sizes[i], "Test 3, " + sizes[i - 1] + " is greater than " + sizes[i]);
            Assert.IsFalse(sizes[i - 1] >= sizes[i], "Test 3, " + sizes[i - 1] + " is greater than or equal to " + sizes[i]);
            Assert.IsFalse(sizes[i - 1] == sizes[i], "Test 3, " + sizes[i - 1] + " is equal to " + sizes[i]);
            Assert.IsTrue(sizes[i - 1] != sizes[i], "Test 3, " + sizes[i - 1] + " is not equal to " + sizes[i]);
        }

        public void DataMeasurementEqualityTest()
        {
            DataMeasurement last = null;
            DataMeasurement OneGB = new DataMeasurement("1 GB");
            foreach (DataMeasurement.DataType dt in Enum.GetValues(typeof(DataMeasurement.DataType)))
            {
                DataMeasurement next = new DataMeasurement(OneGB.ConvertToType(dt, false), dt);
                sizes.Add(next);
                if (last != null)
                    TestEqualValues(next, last);
                last = next;
            }
        }

        private void TestEqualValues(DataMeasurement left, DataMeasurement right)
        {
            Assert.IsFalse(left > right);
            Assert.IsFalse(left < right);
            Assert.IsTrue(left >= right);
            Assert.IsTrue(left <= right);
            Assert.IsTrue(left == right);
            Assert.IsFalse(left != right);
            Assert.IsTrue(left.Equals(right));
        }

        private void TestLeftIsGreater(DataMeasurement left, DataMeasurement right)
        {
            Assert.IsTrue(left > right);
            Assert.IsFalse(left < right);
            Assert.IsTrue(left >= right);
            Assert.IsFalse(left <= right);
            Assert.IsFalse(left == right);
            Assert.IsTrue(left != right);
        }

        private void TestRightIsGreater(DataMeasurement left, DataMeasurement right)
        {
            Assert.IsFalse(left > right);
            Assert.IsTrue(left < right);
            Assert.IsFalse(left >= right);
            Assert.IsTrue(left <= right);
            Assert.IsFalse(left == right);
            Assert.IsTrue(left != right);
        }
    }
}

I hope this object helps save you time.

Leave a Reply