Export (0) Print
Expand All

Anonymous Types in Visual Basic

Visual Studio 2008

Alexandre Moura, Visual Basic QAMicrosoft Corporation

April 2008

Visual Studio 2008 introduces types that do not have a type name – these are called anonymous types. In this walkthrough, you'll see what happens behind the scenes when anonymous types are created, and how generics are used to reduce the number of implicitly declared types in your assembly. We will look into the resulting Intermediate Language (IL) to better understand what gets created to support the use of an anonymous type. (6 printed pages)

   Microsoft Visual Studio 2008   Microsoft Visual Basic 2008

Visual Studio 2008 introduces types that do not have a type name – these are called anonymous types. In this walkthrough, you'll see what happens behind the scenes when anonymous types are created, and how generics are used to reduce the number of implicitly declared types in your assembly. We will look into the resulting Intermediate Language (IL) to better understand what gets created to support the use of an anonymous type.

In Visual Basic 2008 the declaration of a new instance changed to allow not specifying a type as long as members are assigned to in a curly brackets delimited list, thus creating an anonymous typed instance. The statement: "New With {.x = 5}" creates an anonymous type instance with a member named x. Its syntax is similar to object initializers except that object initializers specify a type between the "New" and "With" keywords.

Anonymous type members infer their type from the values assigned to them – in the above case, x will have type Integer, the default type for numeric integer literals.


  Module Module1
      Sub Main()
          'Definition of a local anonymous type variable.
          'Type inference (on by default for new projects created with Visual
          'Studio 2008) will cause var1 to be strongly typed, with the type of
          'the expression on the right of the = sign.
          Dim var1 = New With {.x = 5, .y = #1/2/0003#}
      End Sub
  End Module

Sample 1. Anonymous type instance.

Let's take a look at what vb actually does behind the curtains. Copy the above code to a text file (named, for the purpose of this example v.vb) . Open the Visual studio command prompt (Start\All programs\Microsoft Visual Studio 2008\Visual Studio Tools\Visual Studio 2008 Command Prompt) and type vbc.exe v.vb – this will compile the above text into v.Exe . (This is also done behind the scenes in the Visual Studio IDE.)

Now type ildasm v.Exe – Ildasm (intermediate language disassembler) allows us to view the IL (Intermediate Language) that constitutes a compiled .NET assembly, so we can see exactly what's going on "under the hood". Ildasm will open once you type this line, displaying the IL code generated for this particular assembly by the Visual Basic compiler.

In Ildasm, expanding v.exe, we see the following tree structure:


 v.exe
     Manifest
     My
     System.Xml.Linq
     Module1
     VB$AnonymousType_0'2<T0,T1>

We can see several of the elements included in the assembly: its manifest, the namespaces My and System.Xml.Linq, Module Module1, and a distinctively named class: VB$AnonymousType_0`2<T0,T1> - as you may now suspect, this is the anonymous type that the compiler created to define our instance.

So why such a distinctive name? The reason is we wanted to guarantee the uniqueness of the name, plus discourage people from actually using the name in code (at this point, that's not supported). Let's break the name down. "VB" identifies the type as being generated by the Visual Basic compiler, as opposed to another language or user type. The "$" symbol was chosen because it's not allowed in Visual Basic, meaning that types defined in code can never collide in name with this type. "$" is a valid character for a type name in IL – but you cannot define a Visual Basic type that has that symbol in its identifier. So the Visual Basic compiler emits a type that cannot collide with an already existing Visual Basic type. Next, "AnonymousType" simply indicates that this is an anonymous type. We still have: _0'2<T0,T1> - I'll skip the "_0" for the moment and focus on “`2<T0,T1>". This identifies the type as a generic type, with two type parameters – it's the equivalent on a type definition of "(of T0, T1)" in Visual Basic – the "'2" specifies that it has two type parameters, and <T0,T1> specify what their names are. Going back to _0, the reason this is added is so that we can insert more than one anonymous type with the same number of Type Parameters. We'll get to why we would want to do that further ahead.

Let's expand the type in ILDASM. We should see the following tree:


  v.exe
      Manifest
      My
      VB$AnonymousType_0`2<T0,T1>
        .class private auto ansi
        .custom instance void [mscorlib] System.Diagnostics.DebuggerDisplayAttribute::.ctor(string)
        = (...)
        .custom instance void [mscorlib] System.Diagnostics.DebuggerDisplayAttribute::.ctor() = (...)
        $x : private !0
        $y : private !1
        .ctor : void(!T0,!T1)
        ToString : string()
        get_x : !T0()
        get_y : !T1()
        set_x : void(!T0)
        set_y : void(!T1)
        x : instance !0()
        y : instance !1()

      Module1
  

We now have a bit more information. The class is private meaning that it can only be accessed in this assembly – a second assembly that creates an anonymous type will have to ignore this class definition). It has two public properties, x and y, and two private data members, $x and $y – they have types T0 and T1 respectively. Properties are used to expose the members. While data members and properties can be accessed in the same way, this is not universal – different languages may have different syntaxes to access one or the other (For example, VB allows to access a property with parenthesis – were this property changed to a data member, code that accessed the element using parenthesis would now result in a compiler error. While this particular example goes against the order in which changes might happen if we had gone with exposing data members on anonymous types, it's representative of the kind of issues that we're trying to avoid.). As for the types of the properties, why are they T0 and T1, rather than Integer and Date? Well, the idea is to reduce the amount of anonymous types that are actually created. I'll come back to this later.

Also note that Equals has been overridden. We won't go through the code in IL, but it basically compares each member of the class (default behavior for reference type instances is to compare the reference of the instances themselves, so two different instances whose members compare as equal will still compare as unequal– for anonymous types, they will compare as equal). See the anonymous types conceptual topic for further discussion on comparing anonymous types.

Finally, notice that ToString was also overridden – calling it will return not the typical type name (since it's supposed to be an anonymous type), but a string containing the instance's member values – for example, calling ToString on var1 from the example above will return "{ x = 5, y = 1/2/0003 12:00:00 AM }"  (The actual date format you may see depends on your locale).

So why does an anonymous type use generic type members internally? Take the following code, which exposes two anonymous type definitions, the first with properties x as Integer, y as Date, and the second with x as Char, y as Double:


 Module Module1
     Sub Main()
         Dim var1 = New With { .x = 5, .y = #1/2/0003# }
         Dim var2 = New With { .x = "C"c, .y = 1.2345 }
     End Sub
 End Module

Sample 2. Two anonymous type instances, showing type merging.

Save the above code in a text file, compile the code with vbc.exe and open the resulting .exe with ildasm (make sure to close the other window of ildasm if you use the same text file to build this sample). We see the following tree:


 v.exe
     Manifest
     My
     System.Xml.Linq
     Module1
     VB$AnonymousType_0`2<T0,T1>

Note that we still only have one anonymous type definition, even though there are two distinct instances in the code. What happens is that as long as the name and order of the members is the same, the same generic type can be used to create the instances – and so only one extra class has to be added to the assembly.

There is an interesting side effect to this. If you create two instances with the same type, the instantiated generic type is the same. You can assign the variables that infer those types one to the other, if you so wish. but if they differ in type, name or order, the assignment will result in an error:


 Module Module1
     Sub Main()
         Dim var1 = New With { .x = 5, .y = #1/2/0003# }
         Dim var2 = New With { .x = 2, .y = #1/2/4# }
         Dim var3 = New With { .x = CShort(2), .y = #1/2/4# }
         var1 = var2 'no error
         var1 = var3 'compiler error.
     End Sub
 End Module

Sample 3. Showing that Var1 and Var2's variable type is the same, while Var3 is not.

This is only possible if the names and order stay the same – if either changes, a separate class will be created – this is where the "_0" on the anonymous type class name comes handy:


 Module Module1

    Sub Main()
        Dim Var1 = New With { .x = 5, .y = #1/2/0003# }
        Dim Var2 = New With {.y = #1/2/4# , .x = 2}
    End Sub
 End Module

Sample 4. Two anonymous instances that result in two anonymous type classes

The above code results in the following if you analyze it in ildasm:


 v.exe
    Manifest
    My
    System.Xml.Linq
    Module1
    VB$AnonymousType_0`2<T0,T1>
    VB$AnonymousType_1`2<T0,T1>
    

Note that the second class is named almost exactly like the first – only the "_0" and "_1" terminations are different.

One of the ways anonymous types are useful is in LINQ queries. Let's see how.


 Module Module1
    Sub Main()
       Dim var = From elem In New Integer() {1, 2, 3, 4, 5, 6} _
          Where elem > 3 _
          Select x = elem, xx = elem ^ 2
       Console.WriteLine(var(0).GetType.Name)
       For Each elem In var
          Console.WriteLine(elem.x & "^2 = " & elem.xx)
       Next
    End Sub
 End Module

Sample 5. Linq query sample showing anonymous type of query elements.

In the above sample, we query over an array, and select element members, which we name x and xx. Doing so creates an anonymous type with the specified members. That can be seen by checking the type of any element that is returned from the query, as we do in the above code which will print out:


 VB$AnonymousType_0'2
 4^2 = 16
 5^2 = 25
 6^2 = 36

Without anonymous types, a user would have to manually create a type to hold the values to be used in the query, and create and initialize its instances. While the compiler could automatically create such a type and not hide it, that would likely lead to cluttered code as new anonymous types get created for subsequent queries.

In this document we have covered Anonymous types, a new feature in Visual Basic 2008 – from their creation in code to their implementation by the Visual Basic compiler, by inspecting the IL generated by the mentioned compiler. We have seen that generic classes are created to reduce the number of classes that are required to support different anonymous types, and a sample of their usage in LINQ queries.

Community Additions

ADD
Show:
© 2014 Microsoft