Выбор между неизменяемыми объектами и структурами для объектов стоимости - PullRequest
21 голосов
/ 23 февраля 2009

Как вы выбираете между реализацией объекта-значения (каноническим примером является адрес) как неизменяемого объекта или структуры?

Есть ли у вас производительность, семантика или какие-либо другие преимущества выбора одного над другим?

Ответы [ 11 ]

15 голосов
/ 23 февраля 2009

Есть несколько вещей, которые следует учитывать:

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

Класс выделяется в куче. Это ссылочный тип, поэтому передача объекта через методы не так затратна.

Обычно я использую структуры для неизменяемых объектов, которые не очень велики. Я использую их только тогда, когда в них содержится ограниченное количество данных или я хочу неизменности. Примером является DateTime структура. Мне нравится думать, что если мой объект не такой легкий, как DateTime, его, вероятно, не стоит использовать в качестве структуры. Кроме того, если мой объект не имеет смысла передаваться как тип значения (также как DateTime), тогда это может быть бесполезно использовать в качестве структуры. Неизменность является ключевым здесь, хотя. Также я хочу подчеркнуть, что структуры не неизменны по умолчанию. Вы должны сделать их неизменными по замыслу.

В 99% ситуаций, с которыми я сталкиваюсь, класс - это то, что нужно использовать. Мне часто не нужны постоянные занятия. Для меня более естественно думать о классах как изменчивых в большинстве случаев.

14 голосов
/ 23 февраля 2009

Мне нравится использовать мысленный эксперимент:

Имеет ли смысл этот объект, когда вызывается только пустой конструктор?

Редактировать в запрос Ричарда E

Хорошее использование struct - это обернуть примитивы и ограничить их допустимыми диапазонами.

Например, вероятность имеет допустимый диапазон 0-1. Использование десятичного числа для представления этого везде подвержено ошибкам и требует проверки в каждой точке использования.

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

Вот пример использования struct для представления вероятности:

public struct Probability : IEquatable<Probability>, IComparable<Probability>
{
    public static bool operator ==(Probability x, Probability y)
    {
        return x.Equals(y);
    }

    public static bool operator !=(Probability x, Probability y)
    {
        return !(x == y);
    }

    public static bool operator >(Probability x, Probability y)
    {
        return x.CompareTo(y) > 0;
    }

    public static bool operator <(Probability x, Probability y)
    {
        return x.CompareTo(y) < 0;
    }

    public static Probability operator +(Probability x, Probability y)
    {
        return new Probability(x._value + y._value);
    }

    public static Probability operator -(Probability x, Probability y)
    {
        return new Probability(x._value - y._value);
    }

    private decimal _value;

    public Probability(decimal value) : this()
    {
        if(value < 0 || value > 1)
        {
            throw new ArgumentOutOfRangeException("value");
        }

        _value = value;
    }

    public override bool Equals(object obj)
    {
        return obj is Probability && Equals((Probability) obj);
    }

    public override int GetHashCode()
    {
        return _value.GetHashCode();
    }

    public override string ToString()
    {
        return (_value * 100).ToString() + "%";
    }

    public bool Equals(Probability other)
    {
        return other._value.Equals(_value);
    }

    public int CompareTo(Probability other)
    {
        return _value.CompareTo(other._value);
    }

    public decimal ToDouble()
    {
        return _value;
    }

    public decimal WeightOutcome(double outcome)
    {
        return _value * outcome;
    }
}
13 голосов
/ 25 февраля 2009

Как вы выбираете между реализацией объекта-значения (каноническим примером является адрес) как неизменяемого объекта или структуры?

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

  • Класс
    • изменяемые
    • неизменное
  • Struct
    • изменяемые
    • неизменное

Я утверждаю, что в .NET по умолчанию должен быть изменяемый класс для представления логики и неизменный класс для представления сущности . Я на самом деле склонен выбирать неизменяемые классы даже для логических реализаций, если это вообще возможно. Структуры должны быть зарезервированы для небольших типов, которые имитируют семантику значений, например пользовательский Date тип, Complex числовой тип схожих объектов. Акцент здесь делается на small , так как вы не хотите копировать большие двоичные объекты данных, а косвенное обращение по ссылкам на самом деле дешево (поэтому мы не сильно выигрываем от использования структур). Я стремлюсь сделать структуры всегда неизменными (на данный момент я не могу придумать ни одного исключения). Поскольку это наилучшим образом соответствует семантике внутренних типов значений, я считаю правильным следовать этому правилу.

6 голосов
/ 23 февраля 2009

Факторы: конструкция, требования к памяти, бокс.

Обычно ограничения конструктора для структур - без явных конструкторов без параметров, без конструкции base - решают, следует ли вообще использовать структуру. Например. если конструктор без параметров не инициализирует элементы значениями по умолчанию, используйте неизменяемый объект.

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

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


Что такое "маленький", что такое "много"?

Затраты на объект (IIRC) составляют 8 байт в 32-битной системе. Обратите внимание, что с несколькими сотнями экземпляров это может уже решить, будет ли внутренний цикл полностью запущен в кеше или вызовет GC. Если вы ожидаете десятки тысяч экземпляров, это может быть разницей между выполнением и сканированием.

Исходя из этого POV, использование структур НЕ является преждевременной оптимизацией.


Итак, как правило :

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

2 голосов
/ 23 февраля 2009

В общем, я бы не рекомендовал структуры для бизнес-объектов. Несмотря на то, что вы МОЖЕТЕ получить небольшую производительность, двигаясь в этом направлении, когда вы работаете в стеке, вы в конечном итоге ограничиваете себя, и конструктор по умолчанию может быть проблемой в некоторых случаях.

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

Структуры подходят для простых типов, поэтому вы видите, что Microsoft использует структуры для большинства типов данных. Аналогичным образом структуры подходят для объектов, которые имеют смысл в стеке. Структура Point, упомянутая в одном из ответов, является хорошим примером.

Как мне решить? Обычно я по умолчанию возражаю против объекта, и, если кажется, что это что-то, что выиграло бы от использования структуры, которая, как правило, была бы довольно простым объектом, который содержит только простые типы, реализованные в виде структур, то я обдумаю это и определю, чувство.

Вы упоминаете адрес в качестве примера. Давайте рассмотрим один, как класс.

public class Address
{
    public string AddressLine1 { get; set; }
    public string AddressLine2 { get; set; }
    public string City { get; set; }
    public string State { get; set; }
    public string PostalCode { get; set; }
}

Продумайте этот объект как структуру. При рассмотрении рассмотрим типы, включенные в этот адрес, «struct», если вы закодировали его таким образом. Вы видите что-нибудь, что может не сработать так, как вы хотите? Рассмотрим потенциальную выгоду производительности (то есть, есть ли она)?

2 голосов
/ 23 февраля 2009

В современном мире (я думаю, C # 3.5) я не вижу необходимости в структурах (РЕДАКТИРОВАТЬ: за исключением некоторых нишевых сценариев).

Аргументы pro-struct, по-видимому, в основном основаны на ощутимом выигрыше в производительности. Я хотел бы увидеть некоторые тесты (которые повторяют сценарий реального мира), которые иллюстрируют это.

Понятие использования структуры для «легких» структур данных кажется мне слишком субъективным. Когда данные перестают быть легкими? Кроме того, когда вы добавляете функциональность в код, который использует структуру, когда вы решите изменить этот тип на класс?

Лично я не могу вспомнить, когда в последний раз я использовал структуру в C #.

Редактировать

Я полагаю, что использование структуры в C # по соображениям производительности является очевидным случаем преждевременной оптимизации *

*, если приложение не было профилировано по производительности и использование класса не было определено как узкое место производительности

Редактировать 2

MSDN Состояния:

Тип структуры подходит для представляющих легкие объекты, такие как точка, прямоугольник и цвет. Хотя можно представить указать как класс, структура более эффективен в некоторых сценариях. За Например, если вы объявите массив 1000 очков объектов, вы будете выделять дополнительная память для ссылки на каждый объект. В этом случае структура дешевле.

Если вам не нужен тип ссылки семантика, класс, который меньше чем 16 байтов может быть более эффективно обрабатывается системой как структура.

1 голос
/ 03 января 2015

Я на самом деле не рекомендую использовать структуры .NET для реализации Value Object. Есть две причины:

  • Структуры не поддерживают наследование
  • ORM плохо справляются с отображением на структуры

Здесь я подробно опишу эту тему: Объясненные объекты значения

0 голосов
/ 15 февраля 2010

Структуры строго для продвинутых пользователей (вместе с out и ref).

Да, структуры могут дать большую производительность при использовании ref, но вы должны увидеть, какую память они используют. Кто контролирует память и т. Д.

Если вы не используете ref и ауты со структурами, они того не стоят, если вас ожидают некоторые неприятные ошибки :-)

0 голосов
/ 23 февраля 2009

С точки зрения объектного моделирования, я ценю структуры, потому что они позволяют мне использовать компилятор для объявления определенных параметров и полей как необнуляемых. Конечно, без специальной семантики конструктора (как в Spec # ) это применимо только к типам с естественным нулевым значением. (Отсюда и ответ Брайана Уотта на «эксперимент»).

0 голосов
/ 23 февраля 2009

Как правило, размер структуры не должен превышать 16 байт, в противном случае передача его между методами может стать более дорогостоящей, чем передача ссылок на объекты, длина которых составляет всего 4 байта (на 32-разрядной машине).

Еще одна проблема - конструктор по умолчанию. Структура всегда имеет конструктор по умолчанию (без параметров и открытый), в противном случае такие операторы, как

T[] array = new T[10]; // array with 10 values

не будет работать.

Кроме того, структуры должны перезаписывать операторы == и != и реализовывать интерфейс IEquatable<T>.

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