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:
- Convert it to any other data size type, 1 GB converts to 1024 MB.
- Compare it to any other data size type, 1 GB = 1024 MB.
- 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.