9

In .NET 4 beta 2, there is the new Numerics namespace with struct BigInteger. The documentation states that it is an immutable type, as I would have expected.

But I'm a little confused by the post-increment operator (++). This defintely seems to mutate the value. The following while-loop works:

static BigInteger Factorial(BigInteger n)
{
    BigInteger result = BigInteger.One;
    BigInteger b = BigInteger.One;

    while (b <= n)
    {
        result = result * b;
        b++;  // immutable ?
    }
    return result;
}

This is what MSDN has to say about the Increment operator:

Because BigInteger objects are immutable, the Increment operator creates a new BigInteger object whose value is one more than the BigInteger object represented by value. Therefore, repeated calls to Increment may be expensive.

All well and fine, I would have understood if I had to use b = b++ but apparently ++ by itself is enough to change a value.

Any thoughts?

H H
  • 263,252
  • 30
  • 330
  • 514

5 Answers5

14

The operators ++ and -- are implemented in terms of the normal + and - operators, so in reality:

b++;

is equivalent to:

var temp = b;
b = b + 1;
<use temp for the expression where b++ was located>

Now, as commented, this might seem like it breaks immutability, but it does not.

You should instead look at this code as doing this:

var temp = b;
b = BigInteger.op_Add(b, 1); // constructs a new BigInteger value
<use temp ...>

This will leave two objects in memory, the original BigInteger value, and the new one, now referenced by b. You can easily check that this is what happens with the following code:

var x = b;
b++;
// now inspect the contents of x and b, and you'll notice that they differ

So the original object did not change, hence it does not break immutability, and to answer the new part of the question, this should be thread-safe.

This is the same thing that happens to strings:

String s1 = s2;
s2 += "More";
// now inspect s1 and s2, they will differ
Lasse V. Karlsen
  • 380,855
  • 102
  • 628
  • 825
  • Sounds reasonable, but it still seems break the immutability. I'll edit the question a little. – H H Oct 28 '09 at 09:12
  • 2
    In `s+= s2` there is a visible assignment. It fits if you accept that `b++` is also an assignment, ie `b = b + 1`. But `++` still feels/looks like it's mutating. – H H Oct 28 '09 at 16:52
3

Since BigInteger is immutable, b++ will be just equivalent to:

BigInteger temp=b;
b=temp+1;

After this operation, temp is recycled by the GC and the memory is freed.

Ted Lee
  • 63
  • 4
0
BigInteger b = BigInteger.One;

b++;  // immutable ?

In your example b is a variable, which is just a slot of memory in the current method's stack frame. It is initialised to One, and b++ takes b, creates a new BigInteger (with the incremented value) and returns it. variable b now has the state from the returned new BigInteger.

To be honest immutability as a concept is much clearer when dealing with reference types, because there is an object on the heap whose internal state never changes, so when an operation/method returns a new object with different state it's kind of obvious (e.g. you can do an object reference equality check with object.ReferenceEquals(object, object).

For value types there is no object on the heap, there is just the slot in memory that contains the bits that are the value.

redcalx
  • 8,177
  • 4
  • 56
  • 105
-1

Ok, but what about the unary negation operator that is defined on BigInteger:

public static BigInteger operator -(BigInteger value)
{
    value._sign = -value._sign;
    return value;
}

it appears to break the immutability pattern and directly mutate the BigInteger object. So

b = -b;

actually changes an existing BigInteger in place without returning a new object.

ssabey
  • 1
-1

from the microsoft docs, right after they talk about ++:
Although this example appears to modify the value of the existing object, this is not the case.
BigInteger objects are immutable, which means that internally, the common language runtime actually creates a new BigInteger object and assigns it a value one greater than its previous value. This new object is then returned to the caller.

GideonMax
  • 526
  • 4
  • 11
  • No, BigInteger is a struct (Value Type) and there is no pointer (reference) here. – H H Apr 05 '20 at 16:00
  • @HenkHolterman well, the pointer is either in b or in a struct that b points at, either way, some pointer must change. – GideonMax Apr 05 '20 at 16:10
  • @HenkHolterman OK, I'll give you an example, let's say you have a string, which is a value type, and you pass it to a function, if you look in the dissassembly you will see that a single 64/32 bit value is passed as a parameter to the function, since our string probably has more than 64 bits, we can assume that we aren't passing the string, we are passing a pointer, a pointer to a copy of the string, but still, a pointer. – GideonMax Apr 05 '20 at 16:53
  • A string is a reference type. BigInteger is not. There exist better references than the IL code. – H H Apr 05 '20 at 17:13
  • @HenkHolterman my bad, but I've got a better example, if you are passing a large struct to a function, you pass a pointer to the struct even though a struct is a value type. – GideonMax Apr 06 '20 at 08:44
  • Are you confusing this with C or C++ ? – H H Apr 06 '20 at 09:28
  • it seems I am, will edit my answer to be correct, sorry – GideonMax Apr 06 '20 at 10:54
  • And now compare that to the top answer here. – H H Apr 06 '20 at 11:53