Почему все нормально, что эта структура изменчива? Когда допустимы изменчивые структуры?
Эрик Липперт сказал мне, что я должен "стараться всегда делать типы значений неизменяемыми" , поэтому я решил, что должен стараться всегда делать типы значений неизменяемыми.
Но я только что нашел эту внутреннюю мутирующую структуру, System.Web.Util.SimpleBitVector32
, в сборке System.Web
, которая заставляет меня думать, что должна быть веская причина иметь изменяемую структуру. Я предполагаю, что они сделали это таким образом, потому что он работал лучше под тестированием, и они сохранили его внутреннее, чтобы препятствовать его неправильному использованию. Впрочем, это спекуляция.
У меня есть C & P'd источник этой структуры. Что оправдывает конструктивное решение использовать изменчивую структуру? В целом, какие выгоды могут быть достигнуты при подходе и когда эти преимущества достаточно значительны, чтобы оправдать потенциальные недостатки?
[Serializable, StructLayout(LayoutKind.Sequential)]
internal struct SimpleBitVector32
{
private int data;
internal SimpleBitVector32(int data)
{
this.data = data;
}
internal int IntegerValue
{
get { return this.data; }
set { this.data = value; }
}
internal bool this[int bit]
{
get {
return ((this.data & bit) == bit);
}
set {
int data = this.data;
if (value) this.data = data | bit;
else this.data = data & ~bit;
}
}
internal int this[int mask, int offset]
{
get { return ((this.data & mask) >> offset); }
set { this.data = (this.data & ~mask) | (value << offset); }
}
internal void Set(int bit)
{
this.data |= bit;
}
internal void Clear(int bit)
{
this.data &= ~bit;
}
}
Ответы
Ответ 1
Собственно, если вы ищете все классы, содержащие BitVector в .NET framework, вы найдете кучу этих зверей: -)
- System.Collections.Specialized.BitVector32 (единственный публичный...)
- System.Web.Util.SafeBitVector32 (безопасный поток)
- System.Web.Util.SimpleBitVector32
- System.Runtime.Caching.SafeBitVector32 (безопасный поток)
- System.Configuration.SafeBitVector32 (безопасный поток)
- System.Configuration.SimpleBitVector32
И если вы посмотрите здесь, то они находятся в источнике SSCLI (Microsoft Shared Source CLI, aka ROTOR) System.Configuration.SimpleBitVector32, вы найдете этот комментарий:
//
// This is a cut down copy of System.Collections.Specialized.BitVector32. The
// reason this is here is because it is used rather intensively by Control and
// WebControl. As a result, being able to inline this operations results in a
// measurable performance gain, at the expense of some maintainability.
//
[Serializable()]
internal struct SimpleBitVector32
Я считаю, что это все говорит. Я думаю, что System.Web.Util один более сложный, но построенный на тех же основаниях.
Ответ 2
Учитывая, что полезная нагрузка представляет собой 32-разрядное целое число, я бы сказал, что это может быть легко написано как неизменяемая структура, возможно, без влияния на производительность. Если вы вызываете метод мутатора, который изменяет значение 32-битного поля или заменяет 32-разрядную структуру новой 32-разрядной структурой, вы все равно выполняете то же самое, что и в операциях с памятью.
Вероятно, кто-то хотел что-то, что было похоже на массив (хотя на самом деле это всего лишь биты в 32-разрядном целое), поэтому они решили, что хотят использовать синтаксис индексатора с ним, а не менее очевидный .WithTheseBitsChanged() метод, который возвращает новую структуру. Поскольку он не будет использоваться напрямую кем-либо, кроме веб-команды MS, и, возможно, не очень многими людьми даже в веб-команде, я полагаю, что у них было немного больше свободы в дизайнерских решениях, чем у людей, создающих публичные API.
Итак, нет, возможно, не так для производительности - это, вероятно, было лишь некоторым личным предпочтением программиста в стиле кодирования, и никогда не было никаких веских причин для его изменения.
Если вы ищете рекомендации по дизайну, я бы не потратил слишком много времени на поиск кода, который не был отполирован для общественного потребления.
Ответ 3
SimpleBitVector32
является изменчивым, я подозреваю, по тем же причинам, что BitVector32
является изменяемым. На мой взгляд, неизменное руководство - это просто рекомендация; однако для этого нужно иметь действительно хорошую причину.
Считайте также, что Dictionary<TKey, TValue>
- я перейду в некоторые дополнительные подробности здесь. Словарь Entry
struct изменен - вы можете изменить TValue в любое время. Но Entry
логически представляет значение.
Мутируемость должна иметь смысл. Я согласен с @JoeWhite: кто-то хотел что-то, что было похоже на массив (хотя на самом деле это всего лишь биты в 32-битном целое); также, что обе структуры BitVector могли бы быть... неизменными.
Но, как заявка на одеяло, я не согласен с этим, вероятно, был лишь некоторым личным предпочтением программиста в стиле кодирования и больше склонялся к тому, чтобы никогда не было [и нет] каких-либо веских оснований для его изменения. Просто знайте и понимайте ответственность за использование изменяемой структуры.
Edit
Для записи я сердечно согласен, что вы должны всегда пытаться сделать неизменяемую структуру. Если вы обнаружите, что требования диктуют членскую изменчивость, пересматривайте проектное решение и участвуйте в сверстниках.
Обновление
Я не был уверен в своей оценке производительности при рассмотрении измененного типа значений v. Immutable. Однако, как отмечает @David, Эрик Липперт пишет это:
Бывают случаи, когда вам нужно выкручивать каждый последний бит производительности из системы. И в этих сценариях вам иногда приходится компромисс между чистым кодом, чистым, надежным, понятный, предсказуемый, модифицируемый и код, который не является ни одним из выше, но невероятно быстро.
Я выделил чистый, потому что изменяемая структура не соответствует чистому идеалу, что структура должна быть неизменной. Есть побочные эффекты написания изменчивой структуры: подражательность и предсказуемость скомпрометированы, поскольку Эрик продолжает объяснять:
Переменные типы значений... behave таким образом, что многие люди находят глубоко противоречащие друг другу, и тем самым упростить запись багги-кода (или правильный код, который легко превратился в ошибочный код случайно.) Но да, они очень быстры.
Точка, которую Эрик делает, заключается в том, что вы, как разработчик и/или разработчик, должны принять осознанное и информированное решение. Как вы узнаете? Эрик объясняет это также:
Я бы рассмотрел кодирование двух эталонных решений - один из них mutable structs, один с использованием неизменяемых структур - и запускать некоторые реалистичные ориентированные на пользователя сценарии. Но вот что: не выбирайте быстрее. Вместо этого решите, ПЕРЕД тем, как вы запустите тест как медленный неприемлемо медленный.
Мы знаем, что изменение типа значения быстрее, чем создание нового типа значения; но с учетом правильности:
Если оба решения приемлемы, выберите тот, который чист, правильно и достаточно быстро.
Ключ достаточно быстрый, чтобы компенсировать побочные эффекты выбора изменяемого по неизменяемому. Только вы можете это определить.
Ответ 4
Использование структуры для 32- или 64-битного вектора, как показано здесь, является разумным, с несколькими оговорками:
- Я бы рекомендовал использовать цикл Interlocked.CompareExchange при выполнении любых обновлений структуры, а не просто использовать обычные булевы операторы напрямую. Если один поток пытается записать бит 3, а другой пытается записать бит 8, ни одна из них не должна мешать другому, задерживая его немного. Использование цикла Interlocked.CompareExchange предотвратит возможность ошибочного поведения (поток 1 считывает значение, поток 2 читает старое значение, поток 1 записывает новое значение, поток 2 записывает значение, вычисленное на основе старого значения, и отменяет изменение потока 1), не требуя каких-либо другой тип блокировки.
- Следует избегать элементов структуры, кроме настроек свойств, которые изменяют "this". Лучше использовать статический метод, который принимает структуру в качестве эталонного параметра. Вызов элемента структуры, который модифицирует "this", обычно идентичен вызову статического метода, который принимает элемент как ссылочный параметр, как с точки семантики, так и с точки зрения производительности, но есть одно ключевое различие: если кто-то пытается передать структуру только для чтения ссылаясь на статический метод, вы получите ошибку компилятора. В отличие от этого, если вы вызываете в структуре только для чтения метод, который модифицирует "this", не будет никакой ошибки компилятора, но предполагаемая модификация не произойдет. Так как даже измененные структуры могут обрабатываться как доступные только для чтения в определенных контекстах, гораздо лучше получить ошибку компилятора, когда это произойдет, чем иметь код, который будет компилироваться, но не будет работать.
Эрик Липперт любит рассказывать о том, как изменчивые структуры злы, но важно признать его отношение к ним: он один из людей в команде С#, которому поручено создавать функции поддержки языка, такие как закрытие и итераторы. Из-за некоторых дизайнерских решений на раннем этапе создания .net правильная поддержка семантики значения-типа сложна в некоторых контекстах; его работа была бы намного проще, если бы не было каких-либо изменяемых типов значений. Я не жалею Эрика за его точку зрения, но важно отметить, что некоторые принципы, которые могут иметь важное значение в структуре и языке, не так применимы к дизайну приложения.
Ответ 5
Если я правильно понимаю, вы не можете сделать сериализуемую неизменяемую структуру просто используя SerializableAttribute
. Это связано с тем, что во время десериализации сериализатор создает экземпляр по умолчанию для структуры, а затем устанавливает все поля после создания экземпляра. Если они выполняются только для чтения, десериализация завершится неудачно.
Таким образом, структура была изменчивой, иначе потребовалась бы сложная система сериализации.