Fabulous Adventures In Coding
Eric Lippert is a principal developer on the C# compiler team. Learn more about Eric.
I've been meaning to write a blog post about a recent design change that we've made here on the C# team. As you may have gathered from my previous posts, we are introducing anonymous tuple types in C# 3.0. We've recently decided to make instances of those tuple types immutable; once you construct a particular instance, it is stuck with those values forever.
There are a lot of good reasons to make tuples immutable: it ensures that they have stable hash codes, functional-style programming works better on immutable objects (functional programming is all about avoiding side effects; mutating state is a side effect) and concurrent programming works better on immutable objects.
So anyway, I was going to write this big long blog post about all these things, but I don't have to because the guy across the hall from me, Sreekar Choudhary, has started blogging. Sree is our local expert on anonymous types, extension methods and everything to do with the expression evaluator that runs in the debugger (ie, the watch window.) I'm looking forward to seeing what he has to talk about in his new blog.
So will there be any adjustments to the object initializer semantics that will allow normal (non-anonymous) classes to benefit from the changes towards immutability.
I must admit to being of 2 minds about making them immutable. On the plus side, it does avoid the abuse of anonymous tuple types as cheap throw away objects. On the minus side, you can't use them as cheap throw away objects. ;-)
damien: Your proposal requires quite a bit of work for minimal payoff. I would suggest lobbying for C# to support named and optional parameters like VB.NET - which would also give you the same effect, for a lot less work, with far wider benefit.
Hi Andrew, not sure why you consider some language support for immutability a "minimal payoff", nor how you come to evaluate that it would take "quite a bit of work". You also missed the point, which was some language support for immutability, with a side benefit of behaving somewhat like named parameters.
Immutability combined with property initializers was the goal, something that isnt possible now, but with the changes eric mentioned, might have a pleasing symetry if supported beyond just anonymous classes.
I agree with andrew that making anonymous types always immutable is like throwing the baby out with the bathwater. Both mutable and immutable anonymous types should be supported.
OK, how would you implement GetHashCode on a mutable anonymous type?
It's all very well for you guys to theorize about what is and isn't a lot of work for me to do; I'd like to take this opportunity to point out that Sree and I are the ones who actually DO the work, and there are only so many hours in the day.
Assuming that you can come up with a design for mutable anonymous types which doesn't violate framework design guidelines for GetHashCode, the next thing to consider is "what features would you like to see cut so that we have time to implement and test mutable anonymous types?"
GetHashCode should behave for mutable anonymous types as it does for every other mutable type - it should only be a valid operation as long as the instance isnt modified. That means theres no language enforced guarantees. I would implement it as object identity for mutable types (or simply throw an InvalidOperation exception), and for immutable types, I would implement it as you have done. Sometimes I have have the first call to GetHashCode transform my types into an immutable or frozen state.
Personally, I think making GetHashCode a method of every single object instance was a mistake. Some classes just arent meant to be used as keys in a dictionary.
Its only because we love your work that we want to much from you :)
Eric: I'm not suggesting you do anything other than what you are intending - if anything my main argument was with damien's suggestion in his linked posting. i.e., introducing a new form of setter that only exists to be invoked as part of a constructor/initializer is a very specialized change that (potentially) requires quite a bit of mucking around. As opposed to adding support for a mechanism that already exists and utilized by VB.net.
Damien: The language already has support for immutable objects, as demonstrated by the immutable anonymous tuples. All it takes is to is to only have read-only properties (and of course, no methods that act as mutators), and bingo: an immutable object. The only irritation is construction of an immutable object, potentially requiring a silly number of constructors. Your suggestion was allowing constructor-only setters.Mine was optional named parameters in the constructors.
Back to the topic at hand, GetHashCode on a mutable anonymous type.
As Damien points out. the involvement of anonymous types is actually irrelevant - it's related to the use of mutable objects as keys in a dictionary - specifically, how it can break if the object is modified. So the question really comes down to: What are you afraid of? Somebody modifying an object while you depend on it? Sounds like a classic "Doctor, it hurts when I do this" sort of scenario. One option is to do the same sort of sanity checks that occur when modifying a collection while iterating through it - people learned very quickly not to do that.
I must admit that I'm also curious as to how bullet-proof the immutability actually is. For example, must the members also all be immutable types? If the hash code of a member can change, then the overall hash code of your otherwise immutable object will change anyway? ie you can't be just be a little bit immutable.
Generally, these mutability issues exist throughout C#, and so the approach here is just different. Additionally, I can see how mutable anonymous tuples could be independently handy.
I'll also echo Damien's comments - you do fabulous work - this just happens to be an area I'm interested in (mutability vs immutability).
> On the plus side, it does avoid the abuse of anonymous tuple types as cheap throw away objects. On the minus side, you can't use them as cheap throw away objects. ;-)
I don't see why that would be. Having immutable anonymous types and immutability support at the framework level means that you can optimize operations for an immutable context and make them cheap.
Many functional languages have only immutable types -- anonymous or not -- and do just fine, Erlang's or Haskell's tuples are completely immutable yet they _are_ cheap throaway objects used to temporarily bind "unrelated" structures together in a single (potentially strongly typed) container.
Plus, of course and as Eric pointed out, having immutable dadatypes makes many issues much easier to deal with, at the forefront of which are concurrency/parallelism/distribution (if you have immutable datatypes you can't get "wedged" objects and many issues related to concurrent access to data just disappear) and correctness (if types are immutable, then functions can only manipulate fixed sets of data, thus testing them becomes much easier and the original parameters stay valid)
I'm not arguing the benefits of immutability - it leads to completely declarative languages which are a Good Thing(TM).
I'm not even really arguing whether anonymous tuples should be immutable or mutable - that decision has already been made, and there's no reason to revisit it because of some anonymous comments on Eric's blog.
My only "curiousity" at this point is whether the change actually accomplishes the desired result. i.e., If a read-only tuple contains references to mutable objects, can it really be considered to be immutable?
Re: some classes aren't meant to be used as keys.
Yes, but the primary design criterion of anonymous tuples is PRECISELY that they be usable as keys in a hash table. Otherwise you can't do LINQ joins on tuples.
And though the documentation for GetHashCode wimps out and says that the hash code must be constant only insofar as the object is not mutated, the documentation for Hashtable does not. It calls out that objects used as keys must be immutable.
One of the recent changes we made to anonymous types was to allow the syntax to convey the notion of