Определение нового типа в С#

Я ищу возможности определить новый тип и использовать его в С#, как показано ниже:

Определение класса:

public class Position
{
    public double180 Longitude { get; set; } // double180 is a type within a range -180 and 180
    public double90 Latitude { get; set; } // double90 is a type within a range of -90 and 90
}

Использование:

var position = new Position
{
     Longitude = 45,
     Latitude = 96 // This line should give an error while initializing the object
};

Ответ 1

Тип может быть излишним, но если вы этого хотите, это хороший старт:

struct Double180 : IEquatable<Double180>
{
    private readonly double value;

    public Double180(double d)
    {
        if (d < -180 || d > 180)
        {
            throw new ArgumentOutOfRangeException("d");
        }

        this.value = d;
    }

    public static implicit operator double(Double180 d)
    {
        return d.value;
    }

    public static explicit operator Double180(double d)
    {
        return new Double180(d);
    }

    public override string ToString()
    {
        return this.value.ToString();
    }

    public bool Equals(Double180 other)
    {
        return this.value == other.value;
    }

    public override bool Equals(object obj)
    {
        return obj is Double180 && this.Equals((Double180)obj);
    }

    public override int GetHashCode()
    {
        return this.value.GetHashCode();
    }

    public static bool operator ==(Double180 a, Double180 b)
    {
        return a.Equals(b);
    }

    public static bool operator !=(Double180 a, Double180 b)
    {
        return !a.Equals(b);
    }
}

Конечно, существует много других интерфейсов для реализации, например IConvertible и IComparable<Double180> было бы хорошо.

Как вы можете видеть, вы знаете, где это начинается, но вы не знаете, где он заканчивается.

Валидатор сеттера, как это было предложено другими ответами, может быть лучшей идеей.

Ответ 2

Для этого необязательно нужен новый тип. Вместо использования свойства auto вы можете вручную написать сеттер, который проверяет значение:

public double Latitude
{
    get
    {
        return mLatitude;
    }

    set
    {
        if (value > 90 || value < -90)
        {
            throw new ArgumentOutOfRangeException("Invalid latitude");
        }

        mLatitude = value;
    }
}

private double mLatitude;

Если вы хотите повторно использовать этот код, вы можете определить свой собственный тип и использовать в нем вышеупомянутый наборщик; затем предоставить соответствующий конструктор и операторы преобразования.

Ответ 3

Вероятно, вам лучше добавить System.ComponentModel.DataAnnotations и использовать [Range] так:

public class Position
{
    [Range(-180, 180)]
    public double Longitude { get; set; }

    [Range(-90, 90)]
    public double Latitude { get; set; }
}

Ответ 4

Используйте double и попросите установщика проверить значение:

private double _longitude;
public double Longitude
{
    get
    {
        return _longitude;
    }
    set
    {
        if(value < -180 || value > 180)
        {
            throw new ArgumentException("value");
        }
        _longitude = value;
    }
}

Ответ 5

Добавьте шаг проверки в сеттер:

private double m_Latitude;

public double Latitude
{
  get{return m_Latitude;}

  set
  {
    if(value < -90 || value > 90) throw new ArgumentException("value");

    m_Latitude = value;
  }
}

Обратите внимание, что при реализации реализации свойства вам нужно будет добавить переменную-член для хранения значения свойства.

Ответ 6

В основном я получил идею: подтверждение ввода внутри сеттера. Когда дело доходит до определения типа, кажется, что Structs являются просто лучшими. Итак, наконец, я буду использовать ниже в своем проекте.

public struct Coordinate
{
    private readonly double _x;
    private readonly double _y;

    /// <summary>
    /// Longitude
    /// </summary>
    public double X
    {
        get { return _x; }
    }

    /// <summary>
    /// Latitude
    /// </summary>
    public double Y
    {
        get { return _y; }
    }

    /// <summary>
    /// Initiates a new coordinate.
    /// </summary>
    /// <param name="x">Longitude [-180, 180]</param>
    /// <param name="y">Latitude [-90, 90]</param>
    public Coordinate(double x, double y)
    {
        if (x < -180 || x > 180)
            throw new ArgumentOutOfRangeException(
                "x", "Longitude value must be in range of -180 and 180.");

        if (y < -90 || y > 90)
            throw new ArgumentOutOfRangeException(
                "y", "Latitude value must be in range of -90 and 90.");

        _x = x;
        _y = y;
    }
}

Тогда я буду использовать как это

var position = new Coordinate(46.32, 34.23);

Спасибо всем за ваши ценные комментарии.

Ответ 7

Мне нравится, что документация является частью системы:

public class Position
{
    /// <summary>
    /// ...
    /// 
    /// A value within a range -180 and 180
    /// </summary>
    public double Longitude { get; set; }

    /// <summary>
    /// ...
    /// 
    /// A value within a range -90 and 180
    /// </summary>
    public double Latitude { get; set; }
}

Все зависимые модули должны быть протестированы в соответствии со спецификацией их зависимости. Разработка с проверкой - это один из способов. Контракт - это другое.

Если вы настаиваете на "defencive programming" с проверкой значений времени выполнения, просто используйте конструктор :

public class Position
{
    /// <summary>
    /// ...
    /// 
    /// A value within a range -180 and 180
    /// </summary>
    public double Longitude { get; private set; }

    /// <summary>
    /// ...
    /// 
    /// A value within a range -90 and 180
    /// </summary>
    public double Latitude { get; private set; }

    public Position(double longitude, double latitude)
    {
        if (longitude < -180 || longitude > 180)
        {
            throw new ArgumentOutOfRangeException();
        }

        if (latitude < -90 || latitude > 90)
        {
            throw new ArgumentOutOfRangeException();
        }

        Longitude = longitude;

        Latitude = latitude;
    }
}

Или используйте конструктор :

public class Position
{
    public double Longitude { get; private set; }

    public double Latitude { get; private set; }

    /// <summary>
    /// Protects from invalid positions. Use <see cref="Position.Builder"/>
    /// </summary>
    private Position() { }

    /// <summary>
    /// Builds valid positions
    /// </summary>
    public class Builder
    {
        public double Longitude { get; set; }

        public double Latitude { get; set; }

        public Position Build()
        {
            if (Longitude < -180 || Longitude > 180)
            {
                throw new ArgumentOutOfRangeException();
            }

            if (Latitude < -90 || Latitude > 90)
            {
                throw new ArgumentOutOfRangeException();
            }

            return new Position() { Latitude = this.Latitude, Longitude = this.Longitude };
        }
    }
}

Использование:

Position p = new Position.Builder()
{
    Latitude = 2,
    Longitude = 5
}.Build();

Резюме:

  • Проверки времени выполнения ( "защитное программирование" ):
    • Открытый сеттер с проверкой (см. другие ответы)
    • Открытый конструктор с проверкой
    • "Шаблон Builder" с выполнением проверок строителя.
  • Проверка времени проверки:
    • Тест-приводом
    • Contract приводом