С#: Почему мутации в readonly structs не ломаются?
В С#, если у вас есть struct
, например:
struct Counter
{
private int _count;
public int Value
{
get { return _count; }
}
public int Increment()
{
return ++_count;
}
}
И у вас есть такая программа:
static readonly Counter counter = new Counter();
static void Main()
{
// print the new value from the increment function
Console.WriteLine(counter.Increment());
// print off the value stored in the item
Console.WriteLine(counter.Value);
}
Выходной сигнал программы будет:
1
0
Это кажется совершенно неправильным. Я ожидал бы, что результат будет равен двум 1s (как если бы Counter
был class
, или if struct Counter : ICounter
и Counter
является ICounter
), или быть ошибкой компиляции. Я понимаю, что обнаружение этого во время компиляции является довольно сложным делом, но это поведение, по-видимому, нарушает логику.
Есть ли причина для такого поведения, выходящего за пределы сложности выполнения?
Ответы
Ответ 1
structs
являются типами значений и, следовательно, имеют тип значений типа. Это означает, что каждый раз, когда вы обращаетесь к структуре, вы в основном работаете с копией значения struct.
В вашем примере вы не меняете оригинал struct
, а только его временную копию.
См. здесь дополнительные пояснения:
Почему изменчивые структуры злы
Ответ 2
В .net метод экземпляра struct семантически эквивалентен статическому структурному методу с дополнительным параметром ref
типа struct. Таким образом, учитывая объявления:
struct Blah {
public int value;
public void Add(int Amount) { value += Amount; }
public static void Add(ref Blah it; int Amount; it.value += Amount;}
}
Метод вызывает:
someBlah.Add(5);
Blah.Add(ref someBlah, 5);
являются семантически эквивалентными, за исключением одного отличия: последний вызов разрешен только в том случае, если someBlah
является изменчивым местом хранения (переменная, поле и т.д.), а не если это место хранения только для чтения или временное значение (результат считывания свойства и т.д.).
Это столкнулось с разработчиками .net-языков с проблемой: запрет использования каких-либо функций-членов в структурах только для чтения будет раздражать, но они не хотят позволять функциям-членам писать переменные только для чтения. Они решили "пунтировать" и сделать так, чтобы вызов метода экземпляра в структуре только для чтения создавал копию структуры, вызывал эту функцию и затем отбрасывал ее. Это приводит к замедлению вызовов методам экземпляра, которые не записывают основную структуру, и делают это так, что попытка использовать метод, который обновляет базовую структуру в структуре только для чтения, приведет к разной разбитой семантике из того, что будет если она была передана непосредственно структурой. Обратите внимание, что дополнительное время, затраченное копией, почти никогда не даст правильной семантики в случаях, которые не были бы правильными без копии.
Один из моих главных моментов в .net заключается в том, что до сих пор (по крайней мере, 4.0 и, возможно, 4.5) все еще нет атрибута, через который функция-член структуры может указывать, изменяет ли она this
. Люди релят о том, как структуры должны быть неизменными, а не предоставлять инструменты, позволяющие структурам безопасно предлагать мутирующие методы. Это, несмотря на то, что так называемые "неизменные" структуры являются ложью. Все нетривиальные типы значений в изменяемых местах хранения изменяемы, как и все типы значений в штучной упаковке. Создание "неизменяемой" структуры может заставить переписать целую структуру, когда нужно только изменить одно поле, но поскольку struct1 = struct2
мутирует struct1, копируя все общедоступные и частные поля из struct2, и нет ничего такого определения типа для структуры может сделать это, чтобы исключить (кроме полей) никаких действий, чтобы предотвратить непредвиденную мутацию членов структуры. Кроме того, из-за проблем с потоками структуры очень ограничены в своей способности применять любые инвариантные отношения между их полями. ИМХО, было бы лучше, если бы структура имела возможность разрешить произвольный доступ к полям, давая понять, что любой код, получающий структуру, должен проверять, соответствуют ли его поля всем требуемым условиям, чем пытаться предотвратить формирование структур, которые не соответствуют условиям.