Почему типы чисел C # неизменяемы? - PullRequest
18 голосов
/ 21 октября 2010

Почему int s и double s неизменны? Какова цель возврата нового объекта каждый раз, когда вы хотите изменить значение?

Причина, по которой я спрашиваю, заключается в том, что я делаю класс: BoundedInt, который имеет значение и верхнюю и нижнюю границы. Поэтому мне было интересно: должен ли я сделать этот тип неизменным? (Или это должно быть struct?)

Ответы [ 6 ]

34 голосов
/ 21 октября 2010

Во-первых:

Какова цель возврата нового объекта каждый раз, когда вы хотите изменить значение?

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

Во-вторых: вот очень простой пример того, почему числа неизменны:

5.Increase(1);
Console.WriteLine(5); // What should happen here?

Конечно, это надуманный пример. Итак, давайте рассмотрим еще пару идей.

Изменяемый тип ссылки

Во-первых, вот что: что если Integer был изменяемым ссылочным типом?

class Integer
{
    public int Value;
}

Тогда у нас может быть такой код:

class Something
{
    public Integer Integer { get; set; }
}

И

Integer x = new Integer { Value = 10 };

Something t1 = new Something();
t1.Integer = x;

Something t2 = new Something();
t2.Integer = t1.Integer;

t1.Integer.Value += 1;

Console.WriteLine(t2.Integer.Value); // Would output 11

Кажется, это противоречит интуиции: строка t2.Integer = t1.Integer просто скопирует значение (на самом деле, да; но это "значение" на самом деле является ссылкой) и, таким образом, t2.Integer останется независимым от t1.Integer .

Тип изменяемого значения

К этому можно подойти по-другому, конечно, сохранив Integer как тип значения, но сохранив его изменчивость:

struct Integer
{
    public int Value;

    // just for kicks
    public static implicit operator Integer(int value)
    {
        return new Integer { Value = value };
    }
}

Но теперь допустим, что мы делаем это:

Integer x = 10;

Something t = new Something();
t.Integer = x;

t.Integer.Value += 1; // This actually won't compile; but if it did,
                      // it would be modifying a copy of t.Integer, leaving
                      // the actual value at t.Integer unchanged.

Console.WriteLine(t.Integer.Value); // would still output 10

По сути, неизменность значений - это то, что очень интуитивно понятно . Противоположность крайне не интуитивна.

Полагаю, это субъективно, если честно;)

7 голосов
/ 21 октября 2010

Как изменяемый объект, вы должны заблокировать переменную int перед ее изменением (в любом многопоточном коде, который пишет в ваш int из отдельных потоков).

Почему?Допустим, вы увеличивали int, например:

myInt++

Под капотом это 32-разрядное число.Теоретически, на 32-битном компьютере вы можете добавить 1 к нему, и эта операция может быть атомарной;то есть это будет выполнено за один шаг, потому что это будет выполнено в регистре процессора.К сожалению, это не так;там происходит больше, чем это.

Что если другой поток изменяет это число, пока оно находится в процессе увеличения?Ваш номер будет поврежден.

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

Это основной принцип функционального программирования;делая объекты неизменяемыми и возвращая новые объекты из функций, вы получаете безопасность потоков бесплатно.

5 голосов
/ 21 октября 2010

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

int i = 0;

// Mutation coming!
i += 3;

// The following line will not compile.
3 += 7;

Можно сделать целочисленным полем неизменным, используя readonly. Аналогично, целочисленное свойство может быть только для получения.

3 голосов
/ 21 октября 2010

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

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

2 голосов
/ 21 октября 2010

Все с семантикой значения должно быть неизменным в C #.

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

MyClass o1=new MyClass();
MyClass o2=o1;
o1.Mutate();
//o2 got mutated too
//=> no value but reference semantics

Изменяемые структуры ужасны, потому что вы можете легко вызывать метод мутации для временной переменной. В частности свойства возвращают временные переменные.

MyStruct S1;
MyStruct S2{get;set;}

S1.Mutate(); //Changes S1
S2.Mutate();//Doesn't change S2

Вот почему мне не нравится, что большинство библиотек Vector используют методы мутирования, такие как Normalize, в своей структуре Vector.

0 голосов
/ 17 августа 2014

Я работаю над академическим проектом с нейронными сетями. Эти сети делают тяжелые вычисления с двойниками. Я запускаю его на Amazon Cloud в течение нескольких дней на 32 основных серверах. При профилировании приложения наибольшая проблема с производительностью - это выделение двойного числа! Было бы справедливо иметь выделенное пространство имен с изменяемыми типами. «Небезопасные» ключевые слова могут быть применены для дополнительной предосторожности.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...