Why are C# number types immutable?

2020-05-19 06:09发布

Why are ints and doubles immutable? What is the purpose of returning a new object each time you want to change the value?

The reason I ask is because I'm making a class: BoundedInt, which has a value and an upper and lower bound. So I was wondering: should I make this type immutable too? (Or should it be a struct?)

6条回答
不美不萌又怎样
2楼-- · 2020-05-19 06:23

It makes sense to have BoundedInt as a mutable type because it represents a variable that at any point in time has a specific value and that value can be changed but only within a certain range.

However integers themselves aren't variables so they should not be mutable.

查看更多
家丑人穷心不美
3楼-- · 2020-05-19 06:23

I'm working on an academic project with Neural Networks. These networks do heavy computation with doubles. I run it on amazon cloud for days on 32 core servers. When profiling the application, the top performance problem is allocation of double!! It would be fair to have a dedicated namespace with mutable types. "unsafe" keywords can be enforced for additional precaution.

查看更多
劳资没心,怎么记你
4楼-- · 2020-05-19 06:30

Firstly:

What is the purpose of returning a new object each time you want to change the value?

I think you might be mistaken about how value types work. This isn't some costly operation like you may be imagining; it's simply the overwriting of data (as opposed to, e.g., dynamic allocation of new memory).

Secondly: here's a very simple example of why numbers are immutable:

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

Granted, that is a contrived example. So let's consider a couple more involved ideas.

Mutable reference type

First, there's this one: what if Integer were a mutable reference type?

class Integer
{
    public int Value;
}

Then we could have code like this:

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

And:

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

This seems to defy intuition: that the line t2.Integer = t1.Integer would simply copy a value (actually, it does; but that "value" is in fact a reference) and thus that t2.Integer would remain independent of t1.Integer.

Mutable value type

This could be approached another way, of course, keeping Integer as a value type but maintaining its mutability:

struct Integer
{
    public int Value;

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

But now let's say we do this:

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

Basically, immutability of values is something that is highly intuitive. The opposite is highly unintuitive.

I guess that is subjective, though, in all fairness ;)

查看更多
Deceive 欺骗
5楼-- · 2020-05-19 06:33

As a mutable object, you have to lock an int variable before you change it (in any multi-threaded code that writes to your int from separate threads).

Why? Let's say you were incrementing an int, like this:

myInt++

Under the hood, this is a 32-bit number. Theoretically, on a 32 bit computer you could add 1 to it, and this operation might be atomic; that is, it would be accomplished in one step, because it would be accomplished in a CPU register. Unfortunately, it's not; there is more going on than this.

What if another thread mutated this number while it was in the middle of being incremented? Your number would get corrupted.

However, if you make a thread-safe copy of your object before you increment it, operate on your thread-safe copy, and return a new object when your increment is complete, you guarantee that your increment is thread safe; it cannot be affected by any operations on the original object that take place on other threads, because you're no longer working with the original object. In effect, you have made your object immutable.

This is the basic principle behind functional programming; by making objects immutable, and returning new objects from functions, you get thread safety for free.

查看更多
爷、活的狠高调
6楼-- · 2020-05-19 06:41

Anything with value semantics should be immutable in C#.

Mutable classes can't have value semantics because you can't override the assignment operator.

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

Mutable structs are ugly because you can easily call a mutating method on a temporary variable. In particular properties return temporary variables.

MyStruct S1;
MyStruct S2{get;set;}

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

That's why I don't like that most Vector libraries use mutating methods like Normalize in their Vector struct.

查看更多
看我几分像从前
7楼-- · 2020-05-19 06:42

Integer variables are mutable. However, integer literals are constants, hence immutable.

int i = 0;

// Mutation coming!
i += 3;

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

It's possible to make an integer field immutable, using readonly. Likewise, an integer property could be get-only.

查看更多
登录 后发表回答