Мотивация:
Читая блог Марка Симанна на тему Code Smell: Automatic Property, он говорит рядом с конец:
Суть в том, что автоматические свойства редко подходят. На самом деле они подходят только тогда, когда тип свойства является типом значения и разрешены все мыслимые значения.
Он приводит int Temperature
в качестве примера неприятного запаха и предлагает лучшее решение - тип значения, зависящий от единицы измерения, например, по Цельсию. Поэтому я решил попробовать написать пользовательский тип значения Цельсия, который инкапсулирует всю логику проверки границ и преобразования типов, в качестве упражнения на то, чтобы быть более 29" rel="noreferrer">SOLID.
Основные требования:
- Недопустимое значение
- Инкапсулирует операции преобразования
- Эффективное преодоление (эквивалентно его замене)
- Максимально интуитивно понятный в использовании (пытаясь понять семантику int)
Реализация:
[System.Diagnostics.DebuggerDisplay("{m_value}")]
public struct Celsius // : IComparable, IFormattable, etc...
{
private int m_value;
public static readonly Celsius MinValue = new Celsius() { m_value = -273 }; // absolute zero
public static readonly Celsius MaxValue = new Celsius() { m_value = int.MaxValue };
private Celsius(int temp)
{
if (temp < Celsius.MinValue)
throw new ArgumentOutOfRangeException("temp", "Value cannot be less then Celsius.MinValue (absolute zero)");
if (temp > Celsius.MaxValue)
throw new ArgumentOutOfRangeException("temp", "Value cannot be more then Celsius.MaxValue");
m_value = temp;
}
public static implicit operator Celsius(int temp)
{
return new Celsius(temp);
}
public static implicit operator int(Celsius c)
{
return c.m_value;
}
// operators for other numeric types...
public override string ToString()
{
return m_value.ToString();
}
// override Equals, HashCode, etc...
}
Тесты:
[TestClass]
public class TestCelsius
{
[TestMethod]
public void QuickTest()
{
Celsius c = 41;
Celsius c2 = c;
int temp = c2;
Assert.AreEqual(41, temp);
Assert.AreEqual("41", c.ToString());
}
[TestMethod]
public void OutOfRangeTest()
{
try
{
Celsius c = -300;
Assert.Fail("Should not be able to assign -300");
}
catch (ArgumentOutOfRangeException)
{
// pass
}
catch (Exception)
{
Assert.Fail("Threw wrong exception");
}
}
}
Вопросы:
- Есть ли способ сделать MinValue/MaxValue константными, а не только для чтения? Глядя на BCL, мне нравится, как определение метаданных int четко указывает MaxValue и MinValue как константы времени компиляции. Как я могу имитировать это? Я не вижу способа создать объект Celsius без вызова конструктора или раскрытия деталей реализации, в которых Celsius хранит int.
- Могу ли я пропустить какие-либо функции удобства использования?
- Есть ли лучший шаблон для создания пользовательского типа значения одного поля?