Шаблон для создания простого и эффективного типа значения

Мотивация:

Читая блог Марка Земанна на Запах кода: Автоматическое свойство , он говорит ближе к концу:

Суть в том, что автоматические свойства редко бывают уместными. Фактически, они уместны, только если тип свойства является типом значения и разрешены все возможные значения.

Он приводит int Temperature в качестве примера неприятного запаха и предлагает лучшее решение - это тип значения, зависящий от единицы измерения, например, Цельсия. Поэтому я решил попробовать написать собственный тип значения Цельсия, который инкапсулирует всю логику проверки границ и преобразования типов, в качестве упражнения, чтобы стать более SOLID .

Основные требования:

  1. Невозможно иметь недопустимое значение
  2. Инкапсулирует операции преобразования
  3. Эффективное копирование (эквивалентно заменяющему int)
  4. Как можно более интуитивно понятно (пытается учесть семантику 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 const вместо readonly? Глядя на BCL, мне нравится, как определение метаданных int четко указывает MaxValue и MinValue как константы времени компиляции. Как я могу это имитировать? Я не вижу способа создать объект Celsius без вызова конструктора или раскрытия деталей реализации, которые Celsius хранит в виде int.
  • Мне не хватает каких-либо функций удобства использования?
  • Есть ли лучший шаблон для создания настраиваемого типа значения одного поля?

24
задан ErnieL 7 November 2011 в 18:41
поделиться