Типовые примитивные типы значений .NET с помощью настраиваемых структур: стоит ли этого?

Я играю с идеей сделать примитивные типы значений .NET более безопасными по типу и более "самодокументирующимися", обернув их в пользовательские struct s. Тем не менее, мне интересно, действительно ли это стоило усилий в реальном программном обеспечении.

(Это "усилие" можно увидеть ниже: необходимость повторять один и тот же шаблон кода. Мы объявляем struct и поэтому не можем использовать наследование для удаления повторения кода, а так как перегруженные операторы должны объявляются static, они должны быть определены для каждого типа отдельно.)

Возьмите этот (по общему признанию, тривиальный) пример:

struct Area
{
    public static implicit operator Area(double x) { return new Area(x); }
    public static implicit operator double(Area area) { return area.x; }

    private Area(double x) { this.x = x; }
    private readonly double x;
}

struct Length
{
    public static implicit operator Length(double x) { return new Length(x); }
    public static implicit operator double(Length length) { return length.x; }

    private Length(double x) { this.x = x; }
    private readonly double x;
}

Оба Area и Length являются в основном double, но дополняют его определенным значением. Если вы определили метод, такой как & hellip;

    Area CalculateAreaOfRectangleWith(Length width, Length height)

& hellip, было бы невозможно напрямую пройти через Area случайно. Пока все хорошо.

НО:. Вы можете легко обойти эту, по-видимому, улучшенную безопасность типов, просто добавив Area в double или временно сохраняя Area в переменной double, а затем передавая это в метод, где a Length ожидается:

    Area a = 10.0;

    double aWithEvilPowers = a;
    … = CalculateAreaOfRectangleWith( (double)a, aWithEvilPowers );

Вопрос:. У кого-нибудь есть опыт широкого использования таких пользовательских типов struct в реальном мире/программном обеспечении для производства? Если да:

  • Имеет ли отношение первичных типов значений в пользовательском struct меньше всего к ошибкам или к более содержательному коду или к любым другим основным преимуществам?

  • Или преимущества пользовательского struct слишком малы для их использования на практике?


P.S.: Прошло около 5 лет с тех пор, как я задал этот вопрос. Я отправляю некоторые из моих впечатлений, которые я сделал с тех пор в качестве отдельного ответа.

Ответ 1

Я сделал это в проекте пару лет назад с некоторыми смешанными результатами. В моем случае это помогло многому отслеживать различные типы идентификаторов и иметь ошибки времени компиляции, когда использовался неправильный тип идентификаторов. И я могу вспомнить несколько случаев, когда это мешало фактическим ошибкам в создании. Итак, это была плюсовая сторона. С отрицательной стороны это было не очень интуитивно для других разработчиков - такой подход не очень распространен, и я думаю, что другие разработчики путались со всеми этими новыми типами. Кроме того, я, похоже, помню, что у нас были проблемы с сериализацией, но я не могу вспомнить детали (извините).

Итак, если вы собираетесь идти по этому маршруту, я бы порекомендовал пару вещей:

1) Убедитесь, что вы сначала поговорите с другими людьми в своей команде, объясните, что вы пытаетесь выполнить, и посмотрите, можете ли вы получить "бай-ин" от всех. Если люди не понимают ценность, вы будете постоянно бороться с менталитетом "какой смысл всего этого дополнительного кода"?

2). Сгенерируйте свой шаблонный код с помощью инструмента, такого как T4. Это облегчит обслуживание кода. В моем случае у нас было около дюжины этих типов, и переход к генерации кода стал намного проще и гораздо менее подвержен ошибкам.

Это мой опыт. Удачи!

Джон

Ответ 2

Это логичный следующий шаг к венгерской нотации, см. превосходную статью от Джоэла здесь http://www.joelonsoftware.com/articles/Wrong.html

Мы сделали что-то подобное в одном проекте /API, где esp. другим разработчикам необходимо было использовать некоторые из наших интерфейсов, и у него было значительно меньше "случаев поддержки ложных срабатываний" - потому что это действительно показало, что было разрешено/необходимо... Я бы заподозрил, что это означает значительно меньше ошибок, хотя мы никогда не делали статистику, потому что результирующие приложения не были нашими...

Ответ 3

В aprox. 5 лет с тех пор, как я задал этот вопрос, я часто играл с определением типов значений struct, но редко делал это. Вот несколько причин, по которым:

  • Это не так, что их выгода слишком мала, но стоимость определения типа значения struct слишком высока. Там задействовано множество шаблонов:

    • Переопределить Equals и реализовать IEquatable<T> и переопределить GetHashCode тоже;
    • Выполнять операторы == и !=;
    • Возможно, реализуем IComparable<T> и операторы <, <=, > и >=, если тип поддерживает упорядочение;
    • Переопределить ToString и реализовать методы преобразования и операции литья типа feom/другим связанным типам.

    Это просто много повторяющейся работы, которая часто не нужна при непосредственном использовании базовых примитивных типов.

  • Часто нет разумного значения по умолчанию (например, для типов значений, таких как почтовые индексы, даты, время и т.д.). Поскольку нельзя запретить конструктор по умолчанию, определение таких типов, как struct, является плохой идеей и определение их как class означает некоторые служебные данные во время выполнения (больше работы для GC, больше разыменования памяти и т.д.)

  • Я действительно не обнаружил, что перенос примитивного типа в семантический struct действительно дает значительные преимущества в отношении безопасности типов; Завершение IntelliSense/кода и хороший выбор имен переменных/параметров могут достичь значительных преимуществ, таких как специализированные типы значений. С другой стороны, как показывает другой ответ, пользовательские типы значений могут быть неинтуитивными для некоторых разработчиков, поэтому в их использовании возникают дополнительные когнитивные накладные расходы.

Были случаи, когда я закончил перенос примитивного типа в struct, обычно, когда на них определены определенные операции (например, тип DecimalDegrees и Radians с методами для преобразования между ними).

Определение равенства и методов сравнения, с другой стороны, не обязательно означает, что я определяю тип настраиваемого значения. Вместо этого я мог бы использовать примитивные типы .NET и предлагать имена с наименованием IEqualityComparer<T> и IComparer<T>.

Ответ 4

Я не часто использую структуры. Одна вещь, которую вы можете рассмотреть, - это сделать свой тип необходимым для измерения. Рассмотрим это:

public enum length_dim { meters, inches }
public enum area_dim { square_meters, square_inches }
public class Area {
    public Area(double a,area_dim dim) { this.area=a; this.dim=dim; }
    public Area(Area a) { this.area = a.area; this.dim = a.dim; }
    public Area(Length l1, Length l2) 
    {
        Debug.Assert(l1.Dim == l2.Dim); 
        this.area = l1.Distance * l1.Distance;
        switch(l1.Dim) { 
            case length_dim.meters: this.dim = square_meters;break;
            case length_dim.inches: this.dim = square_inches; break;
        }
    }
    private double area;
    public double Area { get { return this.area; } }
    private area_dim dim;
    public area_dim Dim { get { return this.dim; } }   
}
public class Length {
    public Length(double dist,length_dim dim)
    { this.distance = dist; this.dim = dim; }
    private length_dim dim;
    public length_dim Dim { get { return this.dim; } }
    private double distance;
    public double Distance { get { return this.distance; } }
}

Обратите внимание, что ничего не может быть создано из двойника. Необходимо указать размер. Мои объекты неизменны. Требование измерения и проверка его предотвратили бы катастрофу Mars Express.

Ответ 5

Я не могу сказать, по моему опыту, если это хорошая идея или нет. У него, конечно, есть плюсы и минусы. Про является то, что вы получаете дополнительную дозу безопасности типа. Зачем принимать двойной угол, если вы можете принять тип, имеющий истинную угловую семантику (градусы к/от радианов, ограниченные значениями градуса 0-360 и т.д.). Con, не распространенный, поэтому может быть запутанным для некоторых разработчиков.

См. эту ссылку для реального примера реального коммерческого продукта, который имеет несколько типов, как вы описываете:

http://geoframework.codeplex.com/

Типы включают Угол, Широта, Долгота, Скорость, Расстояние.