Работает ли использование открытых полей только для чтения для неизменяемых структур?
это правильный способ объявить неизменяемые структуры?
public struct Pair
{
public readonly int x;
public readonly int y;
// Constructor and stuff
}
Я не могу понять, почему это может столкнуться с проблемами, но я просто хотел спросить, чтобы убедиться.
в этом примере я использовал ints. Что делать, если вместо этого я использовал класс,но этот класс также неизменен, например? Это тоже должно сработать, верно?
public struct Pair
{
public readonly (immutableClass) x;
public readonly (immutableClass) y;
// Constructor and stuff
}
(в сторону: я понимаю, что используя свойства более обобщенным и позволяет изменять, но эта структура предназначена буквально для всего хранить два значения. Меня просто интересует вопрос неизменности здесь.)
4 ответа:
Если вы собираетесь использовать структуры, рекомендуется сделать их неизменяемыми.
создание всех полей только для чтения-отличный способ помочь (1) документировать, что структура неизменна, и (2) предотвратить случайные мутации.
однако есть одна морщинка, которая на самом деле по странному совпадению я планировал вести блог на следующей неделе. То есть: только чтение на поле структуры-это ложь. Можно ожидать, что поле только для чтения не может измениться, но, конечно оно может. "readonly" на поле структуры-это декларация, пишущая чеки без денег на ее счете. структура не владеет своим хранилищем, и это то хранилище, которое может мутировать.
например, давайте возьмем свою структуру:
public struct Pair { public readonly int x; public readonly int y; public Pair(int x, int y) { this.x = x; this.y = y; } public void M(ref Pair p) { int oldX = x; int oldY = y; // Something happens here Debug.Assert(x == oldX); Debug.Assert(y == oldY); } }
есть ли что-нибудь, что может произойти в "что-то происходит здесь", что приводит к нарушению утверждений отладки? Конечно.
public void M(ref Pair p) { int oldX = this.x; int oldY = this.y; p = new Pair(0, 0); Debug.Assert(this.x == oldX); Debug.Assert(this.y == oldY); } ... Pair myPair = new Pair(10, 20); myPair.M(ref myPair);
и что теперь происходит? Утверждение нарушено! "это" и "Р" относятся к же место хранения. Место хранения мутирует, и поэтому содержимое "этого" мутирует, потому что это одно и то же. Структура не может обеспечить доступность только для чтения x и y, потому что структура не владеет хранилищем; хранилище-это локальная переменная, которая может свободно мутировать столько, сколько захочет.
вы не можете полагаться на инвариант, что поле только для чтения в структуре никогда не изменяется; единственное, на что вы можете положиться, это то, что вы не можете напишите код, который непосредственно изменяет его. Но с небольшой подлой работой, как это вы можете косвенно изменить его все, что вы хотите.
Смотрите также отличную статью Джо Даффи в блоге по этому вопросу:
http://joeduffyblog.com/2010/07/01/when-is-a-readonly-field-not-readonly/
Это сделало бы его действительно неизменным. Я полагаю, вам лучше добавить конструктор, хотя.
Если все его члены тоже неизменны, это сделает его полностью неизменным. Это могут быть классы или простые значения.
компилятор запретит задание
readonly
полей, а также свойства только для чтения.Я рекомендую использовать свойства только для чтения в основном по причинам открытого интерфейса и привязки данных (которые не будут работать с полями). Если бы это был мой проект, я бы потребовал, чтобы структура/класс была общедоступной. Если это будет внутренний сборке или отдельный класс, я мог не замечать этого сначала и оптимизировать их для чтения позже свойства.
начиная с C# 7.2, теперь вы можете объявить всю структуру неизменяемой:
public readonly struct Pair { public int x; public int y; // Constructor and stuff }
это будет иметь тот же эффект, что и маркировка всех полей как
readonly
, а также будет документировать сам компилятор, что структура является неизменяемой. Это повысит производительность областей, где используется структура, за счет уменьшения количества защитных копий, которые делает компилятор.как отметил в ответ Эрика Липперта, это не мешает самой структуре от того, чтобы быть переназначенным полностью, и, таким образом, обеспечивая эффект его полей, изменяющихся из-под вас. Либо передача по значению, либо использование нового
in
модификатор параметров может быть использован для предотвращения этого:public void DoSomething(in Pair p) { p.x = 0; // illegal p = new Pair(0, 0); // also illegal }