Code Focused
Drill Down on Anonymous Types
Learn the details on the new anonymous types and nullable type features in VB 9 (VB 2008).
- By Bill McCarthy
- 10/01/2007
Technology Toolbox: VB.NET, C#
Anonymous types are a new feature of Visual Basic 9 (2008). They provide a quick and easy way to define simple classes for holding values. Both VB 9 and C# 3.0 provide support for anonymous types, but C# supports only immutable anonymous types, whereas VB supports both mutable and immutable anonymous types.
VB gives multiple ways to define an anonymous type: You can either have the type inferred from the result of a LINQ query, or define the type explicitly with the New keyword. You need to include the With statement with the New keyword to define an anonymous type explicitly:
Dim product = New With {.Name= "widget",
.CostPrice = 1.00}
The Visual Basic compiler generates a type definition for the product, giving it a Name property as String and a CostPrice property as Double. The property types are inferred from the value assigned to them. For numeric constants, you might need to include a type suffix literal to indicate the type, such as 1.0D for a decimal or 1.0F for a single--the "f" stands for "float" (see Table 1).
The type the VB compiler creates for this instance of product is a mutable type. Mutable means that the values can be changed. In this particular instance, both the Name and CostPrice can be changed. In some cases, having the type being mutable can be advantageous. However, if you are adding the items to a dictionary or hashtable, you need the class to maintain a constant hash code. This is complicated by the fact that the hash code must be reasonably unique, so it is typically based on the type's internal values.
To allow you to generate anonymous types that can be immutable (and thus usable as a key in hashtables or for filtering or sorting), VB has the Key modifier, which lets you specify which properties you want to use to generate the key:
Dim product = New With { _
Key .Name= "widget", _
Key .CostPrice = 1.00}
With the Key modifier on both the Name and CostPrice properties, the anonymous type generated for the product variable is now fully immutable. Both Name and CostPrice are now ReadOnly properties. The C# equivalent for the immutable anonymous type for product is this:
var product = new {Name= "widget",
.CostPrice = 1.00}
Note that C# doesn't have any equivalent for the mutable type. This difference between VB and C# is one you might need to pay careful attention to if translating any code from one to the other, because the VB syntax that looks closest to the C# syntax is VB's mutable anonymous type, whereas C#'s syntax is limited strictly to immutable types.
Once you specify the Key modifier on one or more properties for an anonymous type, the compiler generates Overrides for the Equals and GetHashCode methods. In both these methods, the properties that are marked as Key are used in the method calculations. In this example, two instances of the anonymous type are equal if both their Name and CostPrice properties are equal:
Dim product1 = New With { _
Key .Name= "widget", _
Key .CostPrice = 1.00}
Dim product2 = New With { _
Key .Name= "widget", _
Key .CostPrice = 1.00}
Dim bool = product1.Equals(product2)
' bool is True
Of course you don't need to have all properties marked as Key. In VB, you can specify some as being Key properties and others as not, which means VB provides you with significant flexibility. For example, you might be happy with the Name and CostPrice as being ReadOnly, but you might want to add a SalesPrice property that you'd like to be able to modify to run various what-if scenarios for an inventory sale. In VB, the task is easy:
Dim product1 = New With {Key .Name= "widget", _
Key .CostPrice = 1.00, .SalesPrice = 1.50}
This definition of product lets you modify the SalesPrice. Again, in C# there is no equivalent anonymous type.
You can also infer the name for any property in an anonymous type from the name of the variable being assigned. Typically, you'll use this inside a LINQ expression:
Dim query = From prod in Products _
Where prod.Quantity > 10 _
Select New With {Key prod.Name, _
Key prod.CostPrice, _
prod.SalesPrice}
The query would return an IEnuemrable of an anonymous type, where the anonymous type is the same as the one defined for product1 in the earlier examples, with ReadOnly properties Name and CostPrice and a mutable SalesPrice property. In fact, the compiler will reuse the same anonymous type definition if there is a definition in the same assembly the compiler has already generated.
You can simplify the anonymous type definition when writing a LINQ query by omitting the New With and the { }'s, but be aware this creates fully immutable types. In this query, query2, the anonymous type includes the properties Name, Key, and SalesPrice as ReadOnly:
Dim query2 = From prod in Products _
Where prod.Quantity > 10 _
Select prod.Name, prod.CostPrice, _
prod.SalesPrice
Anonymous types can't be referenced by the type's name, so you can pass them out only as Object or by using generic inference. Generic inference is of limited use as the type can only be constrained to being a class, so property information would have to be gained through reflection. There are some handy uses of generic inference with anonymous types, such as when the generic parameter is returned and another variables type is inferred. An example of this generic inference is a function to return a List(Of T) from a parameter array of anonymous types.
Function MakeList(Of T)(ByVal _
ParamArray items() As T) _
As List(Of T)
Return New List(Of T)(items)
End Function
You can then call the MakeList function passing to it a set of anonymous types:
Dim products = MakeList(New With {.Name= "widget", _
.CostPrice = 1.00}, New With {.Name= "cogg", _
.CostPrice = 2.00}, New With {.Name= "sprocket", _
.CostPrice = 2.50})
Note that the products type is inferred and relies on the generic inference of T in the MakeList function.
The big problem with anonymous types is the difficulty you face using them outside the method they are defined in. Even a generic type inference doesn't let you strongly type reference the properties. In many cases, you'll want to use normal named classes instead of anonymous types. Fortunately, the VB team's Refactor! for VB 2008 will include a refactoring that creates a named class definition for you from the anonymous syntax. This means you can even use the anonymous syntax as shorthand for your classes and have Refactor create the Properties and backing fields for you.
6 Tips for Nullable Types
Back in August, I briefly touched on nullable types, and the new ? syntax such as Dim x As Int32? and the new If( , ) operator. But there are a lot of subtleties you need to manage when working with nullable types, so I've put together a list of six things you should know about nullable types. If you can remember these six things, you should find nullable types reasonably smooth sailing.
1. Operators on Nullable types return Nullable types.
Beginning with Visual Basic 2008, you will be able to use the operators of the underlying type of a nullable type. For example, you'll be able to add two Nullable(Of Decimal)s together. In all cases, the return type of the operation is a Nullable type of the underlying operations return type. If the return type for the underlying operation is an Integer, then the return type is Nullable(Of Integer)
Dim x as Int32?
Dim y as Int32?
Dim z = x ^ y
' z is a Nullable(Of Double)
The same rule applies to the comparison operators, less than <, greater="" than="">, not equal to <>, and equal to =. The result from the comparison operators is a Nullable(Of Boolean):
Dim result = (x > y) ' result is Nullable(Of Boolean)
2. Nulls are always propagated.
If either operand is null, then the result of the operation will also be a nullable type that is null (.HasValue is False).
3. Boolean? is implicitly coalesced in If, Where, and While statements.
A special case exists where a Nullable(Of Booelan) will be Coalesced with False for If, Where, and While statements. This means the Nullable(Of Boolean) will be in effect converted to a Boolean, and the case where it is null is converted to False. This allows you to write comparison statements a lot more easily. Without this rule, you'd need to convert the Boolean expression yourself explicitly:
' Use
If (x > y) Then
' instead of:
If (x > y).GetValueOrDefault(False) Then
These two expressions are equivalent to each other.
4. Avoid equality comparisons with Nothing.
With nullable types, you assign the nullable type the value of Nothing to set it to null. (.HasValue returns False). But you can also compare the value to Nothing:
Dim x as Int32?
If x = Nothing Then
You should always avoid this expression because most people are likely to misunderstand what it means. The Nothing part of the expression is evaluated to mean a Nullable(of Int32). Because the comparison operation returns a Nullable type (see Rule 1), the null is propagated (see Rule 2), you have a Nullable(Of Boolean) that is null. This is converted to False (see Rule 3). This means that the If x = Nothing Then expression always evaluates to False.
Instead you should write a check to see whether the nullable type HasValue:
If x.HasValue Then
' Or
If Not x.HasValue Then
5. Avoid using Is and IsNot with nullables.
Visual Basic 2008 introduces another special rule pertaining to the use of the Is operator. Normally, you cannot use the Is operator with ValueTypes (Structures), and Nullable types are ValueTypes. However VB will allow you to use the Is and IsNot operator with Nullable types if used with the Nothing keyword:
Dim x as Int32?
If x Is Nothing Then
Unlike the example of the comparison with Nothing using equality or inequality, the Is Nothing expression returns a Boolean. It is in fact exactly the same as using the .HasValue method. Although it has no side effects, your code will be clearer if you use the .HasValue method instead:
If x.HasValue Then
6. Consider using .GetValueOrDefault rather than If( , ).
The final point you should consider is whether to use the new If( , ) operator or the .GetValueOrDefault method of a nullable type. The two are the same when used on a nullable type:
Dim value As Int32 = If(x, 0)
Dim value As Int32 = x.GetValueOrDefault(0)
I prefer the If( , ) operator, but if your team is unfamiliar with its usage, then the .GetValueOrDefault might be a wiser choice. The main thing to realize is they are essentially the same when working with nullable types.
These rules aren't really difficult, but there are some quirks to be aware of, such as rules 4 and 5. Once you memorize these rules, you'll be ready to safely conquer any nullable type quest that comes your way.
,>
About the Author
Bill McCarthy is an independent consultant based in Australia and is one of the foremost .NET language experts specializing in Visual Basic. He has been a Microsoft MVP for VB for the last nine years and sat in on internal development reviews with the Visual Basic team for the last five years where he helped to steer the languageās future direction. These days he writes his thoughts about language direction on his blog at http://msmvps.com/bill.