0

From this Answer, I came to know that KeyValuePair are immutables.

I browsed through the docs, but could not find any information regarding immutable behavior.

I was wondering how to determine if a type is immutable or not?

Community
  • 1
  • 1
Tilak
  • 30,108
  • 19
  • 83
  • 131

4 Answers4

1

I don't think there's a standard way to do this, since there is no official concept of immutability in C#. The only way I can think of is looking at certain things, indicating a higher probability:

1) All properties of the type have a private set

2) All fields are const/readonly or private

3) There are no methods with obvious/known side effects

4) Also, being a struct generally is a good indication (if it is BCL type or by someone with guidelines for this)

Something like an ImmutabeAttribute would be nice. There are some thoughts here (somewhere down in the comments), but I haven't seen one in "real life" yet.

phipsgabler
  • 20,535
  • 4
  • 40
  • 60
0

The first indication would be that the documentation for the property in the overview says "Gets the key in the key/value pair." The second more definite indication would be in the description of the property itself:

"This property is read/only."

Willem van Rumpt
  • 6,490
  • 2
  • 32
  • 44
  • 1
    The fact that a property is "read-only" says that it "might" be immutable, but in no way implies that it actually is. All "read-only" means in that context is that one cannot change the value of the property using a property setter. For example, `List` has a read-only property `Count`, which cannot be changed by storing the new desired value, but can be changed by adding or removing items from the list. – supercat May 15 '12 at 16:54
  • The OP's question was: "I can't find anywhere in the docs that the Value property of a KeyValuePair can't be changed". It MOST definitely is documented. Quite clearly actually. It says: "You can not assign a value to KeyValuePair.Value". If that's not how the documentation should be read, I suggest to throw out 90% of the documentation. Although I agree with you on a formal basis (i.e., per your example, "Count" is also a property, but can't be changed "directly", it's a derived property), I still stick to my point that the documentation is absolutely, completely clear on the OP's question. – Willem van Rumpt May 15 '12 at 17:11
0

I don't think you can find "proof" of immutability by just looking at the docs, but there are several strong indicators:

  • It's a struct (why does this matter?)
  • It has no settable public properties (both are read-only)
  • It has no obvious mutator methods

For definitive proof I recommend downloading the BCL's reference source from Microsoft or using an IL decompiler to show you how a type would look like in code.

Community
  • 1
  • 1
Jon
  • 428,835
  • 81
  • 738
  • 806
  • i agree on struct part, that was the only clue i had at first glance. It is very difficult to check for settable public properties/obvious mutator method. For example DateTime class provide several static methods which creates new DateTime with modified data. It requires very close look to identify that. – Tilak May 14 '12 at 09:07
  • 1
    I don't agree on the struct part. I see plenty of code where struct fields/properties are initialised not from a constructor, but from the method using the struct. The fact that the struct isn't modified after its fields are set doesn't make it immutable. –  May 14 '12 at 09:08
  • Or, to put it simply, it's good to make structs immutable, but whether a struct actually is mutable depends, just like for classes, on whether it has public fields or mutator methods (including property setters) –  May 14 '12 at 09:09
  • @hvd: Sure, that's why I said indication and not proof. – Jon May 14 '12 at 09:10
  • @Jon And I'm saying it's not even a strong indication :) –  May 14 '12 at 09:12
  • @Jon Actually, on second thought, in general, I disagree, but you're probably right for BCL structs, in that case it probably is a fairly reliable indicator. –  May 14 '12 at 09:18
  • A struct which does not have any direct or nested fields or properties of mutable class types, will have the same degree of mutability *as the storage location in which it is held*. If a class holds a private field of type `Point`, the fields of that class can be written in those ways, and only in those ways, that the class itself writes to them. Making the fields of a struct "immutable" increases the hassle entailed with writing to them, but doesn't really affect who can write them and who cannot. – supercat May 14 '12 at 15:14
  • @supercat: Instances of `Point` remain immutable. I don't see how the fact that you can replace one with another (which of course you can do, otherwise C# would not be an imperative language) plays any role here. – Jon May 14 '12 at 15:18
  • In general, when one asks whether a type is "mutable", the real assurance one seeks is that the contents of the thing referred to by a storage location can't be changed *except by writing to that storage location*. And structs with regard to their public fields (if a field is a mutable class type, the identity of the object referred to by that field within a struct cannot change, though the contents of that object might). – supercat May 14 '12 at 15:20
  • If one says `Threading.Interlocked.Increment(MyPoint.X);` there is no concise way to define the semantics of that operation other than to say `MyPoint` was mutated by increasing `X`. I suppose one could try to say that `MyPoint` was atomically replaced by a new `Point` whose `X` value was one higher than the old one, but such terminology would imply that the system has the ability to atomically update structs; it doesn't. – supercat May 14 '12 at 15:23
  • It's important to note, btw, that saying `myStruct1=myStruct2` mutates `mystruct1` by copying all public and private fields from `myStruct2`. In C#, saying `myStruct1=new MyStructType(1,2,3)` doesn't replace `myStruct1` with a new instance; rather, it creates a new temporary instance, mutates `myStruct1` by copying all the fields from that temporary instance, and then discards that temporary instance. – supercat May 14 '12 at 15:26
  • @supercat: The `Interlocked.Increment` example is very badly chosen because it will not compile without `ref` on the parameter, at which point it should be obvious how the increment is effected. And again, I do not see what *any* of this has to do with immutable types. – Jon May 14 '12 at 15:33
  • To mutate a storage location of a structure type is to mutate its fields, and vice versa. The actual mutability of a non-trivial struct's fields thus depends not upon anything in the struct's definition, but rather the location in which a particular instance is stored. With regard to the `Interlocked.Increment` example, I did forget `ref`; my point is that the operation performed can most reasonably be described in terms of mutating an existing `Point` instance, rather than replacing an instance with a new one. – supercat May 14 '12 at 16:36
0

A KeyValuePair<T1,T2> is a struct which, absent Reflection, can only be mutated outside its constructor by copying the contents of another KeyValuePair<T1,T2> which holds the desired values. Note that the statement:

  MyKeyValuePair = new KeyValuePair(1,2);

like all similar constructor invocations on structures, actually works by creating a new temporary instance of KeyValuePair<int,int> (happens before the constructor itself executes), setting the field values of that instance (done by the constructor), copying all public and private fields of that new temporary instance to MyKeyValuePair, and then discarding the temporary instance.

Consider the following code:

static KeyValuePair MyKeyValuePair;  // Field in some class

// Thread1
  MyKeyValuePair = new KeyValuePair(1,1);
  // ***
  MyKeyValuePair = new KeyValuePair(2,2);

// Thread2
  st = MyKeyValuePair.ToString();

Because MyKeyValuePair is precisely four bytes in length, the second statement in Thread1 will update both fields simultaneously. Despite that, if the second statement in Thread1 executes between Thread2's evaluation of MyKeyValuePair.Key.ToString() and MyKeyValuePair.Value.ToString(), the second ToString() will act upon the new mutated value of the structure, even though the first already-completed ToString()operated upon the value before the mutation.

All non-trivial structs, regardless of how they are declared, have the same immutability rules for their fields: code which can change a struct can change its fields; code which cannot change a struct cannot change its fields. Some structs may force one to go through hoops to change one of their fields, but designing struct types to be "immutable" is neither necessary nor sufficient to ensure the immutability of instances. There are a few reasonable uses of "immutable" struct types, but such use cases if anything require more care than is necessary for structs with exposed public fields.

supercat
  • 77,689
  • 9
  • 166
  • 211