Anonymous Types and Object Identities

One of the recent changes we made to anonymous types was to allow the syntax to convey the notion of object identity. Paul (our architect) has a blog post on this topic, and I wanted to add a little bit to what he said with a few examples.

The C# team decided to make anonymous types immutable (see Eric's blog post on it), but for Visual Basic, we decided that becausee we have the ability to late bind on top of anonymous types, making them immutable is unexpected.

For example, if you have the following:

    1:  Function Tupletize(x as Integer, y as Integer) As Object
    2:      return New With {x, y}
    3:  End Function

You can use latebinding to bind to the fields:

    1:  Dim coord = Tupletize(10, 12)
    2:  Console.Writeline(coord.x & "," & coord.y)

It would seem like a language limitation to make anonymous types immutable by default:

    1:  Dim coord = Tupletize(10, 12)
    2:   
    3:  ' We wanted to let this work
    4:  coord.y = coord.x + coord.y + 12
    5:  Console.Writeline(coord.x & "," & coord.y)

We also see the value (and correctness) of making the fields immutable (when used as a key in a table). For example, if you use an anonymous type as a key to the hash table, you better make sure it's hash code doesn't change. Otherwise, the hash table will break and you won't be able to find keys that you put into the table! The motivating factor for driving the immutable anonymous types was because the LINQ APIs used hash tables internally and returning projections of anonymous types that could be modified was a dangerous situation.

As a result, the VB team decided to allow users to specify the identity of the anonymous type through a declarative keyword:

    1:  Dim x = New With {Key .id = 10, .FirstName = "Tim", .LastName = "Ng"}

When you use the "Key" keyword to decorate a field on the anonymous type, you are telling the compiler that the field(s) marked should determine the object identity. That means that calls to Equal and GetHashCode will only look at key fields. So if you change non key fields, the object's identity is still the same. In addition, because key fields shouldn't change, no setters are synthesized for key fields.

If an anonymous type has no key fields, then it behaves like a reference; that is, two such anonymous types are equal if and only if they are the same instance.

If an anonymous type has all fields marked as key, then the anonymous type is "immutable" (and is equivalent to the anonymous types generated by C#).

Finally, for VB, all queries produced using query syntax generate anonymous types with all fields marked as key.

The caveat here is that you have to remember to make the anonymous types all have key fields if you use extension method syntax for queries, as in the following example:

 Dim q = from c in db.Customers select c.FirstName, c.LastName

' The equivalent anonymous type is:

Dim q = from c in db.Customers select New With {Key c.FirstName, Key c.LastName}

' Likewise for extension method syntax:

Dim q = db.Customers.Select(function(c) New With {Key c.FirstName, Key c.LastName})

We really like the approach that we adopted for this. Let us know what you think!

Technorati tags: VB9, Orcas, AnonymousTypes