With mutable types, the difference in behaviour between value and reference types is clear:
// Mutable value type
PointMutStruct pms1 = new PointMutStruct(1, 2);
PointMutStruct pms2 = pms1;
// pms1 == (1, 2); pms2 == (1, 2);
pms2.X = 3;
Mut开发者_开发百科ateState(pms1); // Changes the X property to 4.
// pms1 == (1, 2); pms2 == (3, 2);
// Mutable reference type
PointMutClass pmc1 = new PointMutClass(1, 2);
PointMutClass pmc2 = pmc1;
// pmc1 == (1, 2); pmc2 == (1, 2);
pmc2.X = 3;
MutateState(pmc1); // Changes the X property to 4.
// pmc1 == (4, 2); pmc2 == (4, 2);
With immutable types however, this difference is less clear cut:
// Immutable value type
PointImmStruct pis1 = new PointImmStruct(1, 2);
PointImmStruct pis2 = pis1;
// pis1 == (1, 2); pis2 == (1, 2);
pis2 = new PointImmStruct(3, pis2.Y);
// Can't mutate pis1
// pis1 == (1, 2); pis2 == (3, 2);
// Immutable reference type
PointImmClass pic1 = new PointImmClass(1, 2);
PointImmClass pic2 = pic1;
// pic1 == (1, 2); pic2 == (1, 2);
pic2 = new PointImmClass(3, pic2.Y);
// Can't mutate pic1 either
// pic1 == (1, 2); pic2 == (3, 2);
Immutable reference types often use value semantics too (e.g. the canonical example System.String
):
string s1 = GenerateTestString(); // Generate identical non-interned strings
string s2 = GenerateTestString(); // by dynamically creating them
// object.ReferenceEquals(strA, strB)) == false;
// strA.Equals(strB) == true
// strA == strB
Eric Lippert has discussed before on his blog (e.g. here) that the fact that value types are often (when doesn't really matter for this discussion) allocated on the stack is an implementation detail and that it shouldn't generally dictate whether you make an object a value or reference type.
Given this blurred distinction in behaviour for immutable types, what criteria does this leave for us to decide whether to make an immutable type a reference type or a value type?
Also, with the immutable emphasis on values vs variables, should immutable types always implement value semantics?
I would say that Eric's blog post you link gives you exactly the answer:
I regret that the documentation does not focus on what is most relevant; by focusing on a largely irrelevant implementation detail, we enlarge the importance of that implementation detail and obscure the importance of what makes a value type semantically useful. I dearly wish that all those articles explaining what “the stack” is would instead spend time explaining what exactly “copied by value” means and how misunderstanding or misusing “copy by value” can cause bugs.
If your objects should have a "copy-by-value" semantic, then make them value types. If they should have a "copy-by-reference" semantic, make them reference types.
He also says this, which I agree with:
I’d always make the choice of value type vs reference type based on whether the type is semantically representing a value or semantically a reference to something.
There is an important category of immutable types (that Eric Lippert has also written about at some length) that must be implemented as reverence types: recursive types such as list nodes, tree nodes, and the like. Value types can't have cyclical definitions, as, for example, a linked list node does:
class IntNode
{
private readonly int value;
private readonly IntNode next;
}
.NET sort of hints at an answer to this with the String
class. It’s immutable, but is a reference type. Make your immutable type act like a value type as much as possible. It doesn’t matter if it actually is a value type.
So the only criteria I can think of is: If copying it is going to be expensive (a String
could involve a lot of copying!), make it a reference type. If it’s quick to copy, go for a value type. Also consider if you need to compare references – that’s probably the only tricky part with immutable reference types.
精彩评论