Подтвердить что ты не робот

Выбор между неизменяемыми объектами и структурами для объектов ценности

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

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

4b9b3361

Ответ 1

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

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

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

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

Ответ 2

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

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

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

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

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

Ответ 3

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

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

Изменить запрос Richard 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;
    }
}

Ответ 4

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

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

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

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


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

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

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


Итак, как эмпирические правила:

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

Ответ 5

В современном мире (я думаю, С# 3.5) я не вижу необходимости в структурах (EDIT: помимо некоторых нишевых сценариев).

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

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

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

Изменить

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

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

Изменить 2

MSDN Государства:

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

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

Ответ 6

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

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

Структуры отлично подходят для простых типов, поэтому вы видите 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", если вы его закодировали таким образом. Вы видите что-то, что может не получиться так, как вы хотите? Рассмотрим потенциальную выгоду от производительности (т.е. Есть ли она)?

Ответ 7

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

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

Здесь я подробно описываю этот раздел: Объекты Value объяснены

Ответ 8

Какова стоимость копирования экземпляров, если они переданы по значению.

Если высокий, то неизменяемый ссылочный (класс) тип, в противном случае тип value (struct).

Ответ 9

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

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

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

не работает.

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

Ответ 10

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

Ответ 11

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

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

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