C #: Почему мутации на readonly структурах не ломаются? - PullRequest
13 голосов
/ 05 октября 2010

В C #, если у вас есть 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

Это кажется совершенно неправильным. Я бы ожидал, что результат будет равен двум 1 (как если бы Counter был class или struct Counter : ICounter и counter был ICounter), или это была бы ошибка компиляции. Я понимаю, что обнаружить это во время компиляции довольно сложно, но такое поведение нарушает логику.

Есть ли причина такого поведения за пределами сложности реализации?

Ответы [ 2 ]

8 голосов
/ 05 октября 2010

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

В вашем примере вы не меняете оригинал struct, а только его временную копию.

См. Здесь для дальнейших объяснений:

Почему изменчивые структуры злые

3 голосов
/ 20 декабря 2012

В .net метод экземпляра структуры семантически эквивалентен методу статической структуры с дополнительным параметром ref типа структуры. Таким образом, с учетом деклараций:

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, и в этом нет ничего определение типа для структуры может сделать, чтобы предотвратить (кроме отсутствия полей), что она ничего не делает, чтобы предотвратить неожиданную мутацию членов структуры. Кроме того, из-за проблем с многопоточностью структуры очень ограничены в своих возможностях применять любые инвариантные отношения между своими полями. ИМХО, как правило, для структуры было бы лучше разрешить произвольный доступ к полям, давая понять, что любой код, получающий структуру, должен проверять, соответствуют ли ее поля всем необходимым условиям, чем пытаться предотвратить формирование структур, которые не удовлетворяют условиям.

...