Basic Instincts

Type inference in Visual Basic 2008

Bill Horst

Contents

Why Is Type Inference Necessary?
Type Inference and Lambda Expressions
Other Implications
Go To Type Definition
Enabling and Disabling Type Inference

In order to support Language Integrated Query (LINQ), a suite of new technologies was added to the upcoming release of Visual Basic® 2008 that includes type inference. In earlier versions of Visual Basic, a member's type was assumed to be Object unless a different type was explicitly declared in code, as shown in Figure 1. A drawback to declaring a variable this way is that the compiler is only able to assume that the value of the variable will be of type Object. The experience for the developer is that IntelliSense® is only able to display information for the members that are common to all Objects, though there may be other members available on the actual instance at run time. With late binding, the other members are bound at run time, which means they will not be verified in any way until it is time for the line to be executed. So, nonexistent members (such as cust.Name in Figure 1) can be referenced in code, and the problem will not be caught by the compiler. This can easily result in a MissingMemberException and generally requires you to keep track of more information on your own.

Figure 1 Lack of Type Inference in Visual Basic 2005

Class Form1
    Sub Main()
        Dim a = 3                   ' Type: Object    Value: 3 {Integer}
        Dim b = DateTime.Now        ' Type: Object    Value: Current Time

        Dim cust = GetCustomer(someID)
        MsgBox(cust.Name) ' Customers have FirstName and LastName fields.
                          ' This code generates an exception at Run-Time.

        Dim procs = Process.GetProcesses
        For Each g In procs   ' Compile Error: 'g' is not declared
            Console.WriteLine(g.Id) 
        Next
    End Sub
End Class

However, with type inference in Visual Basic 2008, the types of method-level members are inferred based on the right-hand side of any assignments in the declaration statements. You can find several examples in Figure 2. Here, the compiler will catch the reference to the nonexistent member cust.Name shown in Figure 1, and IntelliSense information will be provided for all available members, as if the type were specified explicitly. In the For loop shown in Figure 2, the variable g is recognized as a declaration, with the type of the "procs" expression.

Figure 2 Type Inference in Visual Basic 2008

Class Form1
    Sub Main()
        Dim a = 3                   ' Type: Integer   Value: 3
        Dim b = DateTime.Now        ' Type: Date      Value: Current Time

        Dim cust = GetCustomer(someID)
        MsgBox(cust.Name) ' Here we get a compiler error, catching the 
                          ' problem quickly

        Dim procs = Process.GetProcesses
        For Each g In procs         ' Type: Process
            Console.WriteLine(g.Id) ' No compile errors
        Next
    End Sub
End Class

However, there are times when type inference doesn't happen. For local Dim statements, type inference only occurs when there is an assignment on the declaration line. So, assignments made outside the declaration of the variable will cause the compiler to assume that Object is the type. Object is also still inferred as the type for class-level members, so type inference does not apply to functions, subs, properties, class/structure fields, and so on. Some specific examples are shown in Figure 3.

Figure 3 Cases with No Type Inference

Class Form1
    Sub Main()
        Dim d         ' Assignment not on the declaration line
        d = 3         ' Type: Object    Value: 3 {Integer}
    End Sub
                      ' Class-level
    Dim e             ' Type: Object    Value: Nothing
    Dim f = 3         ' Type: Object    Value: 3 {Integer}

    Function Func1()  ' Type: Object    Value: 3 {Integer}
        Return 3
    End Function
End Class

Why Is Type Inference Necessary?

Type inference is necessary in Visual Basic 2008 because of new language constructs that enable the creation of a type that is not explicitly defined anywhere. In order to assign a variable to an expression with a non-explicitly defined type, it is necessary to infer the type, since it would be impossible to specify it in code.

These non-explicitly defined types are called anonymous types. An object can be created and certain members initialized without being previously defined. This enables LINQ statements to specify a varying number of fields (and in different orders) without having to define objects for every possible variant in advance. Because the object does not necessarily match an already-defined type, the compiler generates a type implicitly (because the name of this type is inconsequential and is not exposed to the developer for use in code, it's called an anonymous type). Using the type inference functionality, the compiler is able to treat this object of an anonymous type just as it would treat an object of an explicitly defined type. Anonymous types will be most useful to LINQ users who write SQL-style queries in Visual Basic.

Here's an example of how anonymous types can help in your LINQ development. Suppose you have a database that includes records of your company's orders, and you want to view certain information for all the orders that have shipped to the state of Washington. The following SQL code assumes a table called Orders whose records contain various fields, including ID, Date, Address, City, State, Zip, and Shipped. The query would return all records in the table where the Shipped field has the value True and the State field has the value Washington:

SELECT Orders.ID, Orders.Date AS OrderDate, Orders.Address,
       Orders.City, Orders.State, Orders.Zip
FROM Orders
WHERE Shipped = 'True' AND State = 'Washington'

With the development of LINQ, a similar operation can be performed in Visual Basic. The following code performs this same query on Orders and assigns the result to the variable OrdersShippedToWA. Orders could be a database table, an in-memory array or collection, or any type that implements the IEnumerable interface:

Dim OrdersShippedToWA = From Ord In Orders _
                        Where Ord.Shipped _
                            And Ord.State = "Washington" _
                                Select Ord.ID, OrderDate = Ord.Date, _
                                Ord.Address, Ord.City, _
                                Ord.State, Ord.Zip

The type that will be inferred for OrdersShippedToWA is IEnumerable(Of <anonymous type>). Each element in the IEnumerable will then have the members that were selected in the query—ID, OrderDate, Address, City, State, and Zip. As you can probably imagine, LINQ is much easier to use and more agile if you don't need to define a type for each combination of members you'd like to use in the Select portion of your query expressions. For this reason, the Visual Basic compiler generates an anonymous type containing the appropriate members and infers that type if an element is referred to later in code. In the following For loop, the type of the variable Ord is inferred as the same anonymous type I discussed for OrdersShippedToWA:

For Each Ord In OrdersShippedToWA
    Dim City As String = Ord.City
Next

In this example, the variable Ord is of a type that has not been explicitly declared, but whose member names are known. The background compiler is able to infer the type, and you get the same IntelliSense support to which you are accustomed. This is the primary reason type inference has been added to Visual Basic 2008.

Imagine another scenario where you want to deal with the entire Order object. If only one expression is being selected by the query, the specific type is inferred. If there are methods or other members defined for the type used in the collection, they can be called through type inference, as well:

Dim SortedWAOrders = From Ord In Orders _
                     Select Ord _
                     Order By Ord.ID Ascending

Dim FirstShipTime = SortedWAOrders(0).CalculateShipTime()

In fact, thanks to type inference, you could use your methods in the query itself. Consider the following example, in which the results are returned in order of the ship time calculated by a method:

Dim SortedShipTimes = From Ord In Orders _
                      Select Ord _
                      Order By Ord.CalculateShipTime() Ascending

There may be occasions where it is convenient for you to initialize an anonymous type in code, so this is also supported in Visual Basic. In Figure 4, Part A shows the NewCustomer variable created without an explicit type. Note that the types of the members in the definition are being inferred as well, since they are not defined anywhere. Part B shows another assignment, where a member of the anonymous type is recognized by the compiler. IntelliSense functionality is supported on the variable when it is referred to in the NewCustomerCity assignment. In Part C, a query called SeattleCustomers, which selects members that match the members of the anonymous type, is initialized. In this case, the compiler recognizes that an anonymous type already exists that matches the one needed for this statement. Instead of creating a new anonymous type, the same type is inferred for SeattleCustomers as for NewCustomer. In Part E, the object assigned to NewCustomer can be added to the data from the SeattleCustomers query because type inference recognized the same anonymous type could be used in both places.

Figure 4 Anonymous Types Matched from Query and Variable

'[Part A] Anonymous Type variable declared
Dim NewCustomer = New With {.Name = "Bill Horst", _
                            .Age = 25, _
                            .City = "Seattle"}

'[Part B] Member recognized by Compiler/IntelliSense
Dim NewCustomerCity = NewCustomer.City

'[Part C] Name, Age and City selected from records in 
'         Customers database table
Dim SeattleCustomers = From cust In db.Customers _
                       Where cust.City = "Seattle" _
                       Select cust.Name, cust.Age, cust.City

'[Part D] Seattle Customers stored in a variable with 
'         type List(Of <anonymous type>)
Dim SCList = SeattleCustomers.ToList()

'[Part E] Variable Type and Query Result Type match
SCList.Add(NewCustomer)

Type Inference and Lambda Expressions

Type inference is also helpful in accommodating Lambda expressions. A Lambda expression is a new Visual Basic language feature that allows the use of delegates for simple functions in an inline fashion. This is particularly useful for passing simple delegates as parameters. Without having to even name a function, a Lambda expression can be passed, declaring parameters and the expression to return. In the following example, a function is declared that accepts a Customer and a String and returns a Boolean representing whether or not the Customer's city matches the String. That function is then passed as a delegate to the Filter method, presumably to filter based on this Boolean value:

Orders.Filter( _
    Function(cust As Customer, city As String) cust.City = city)

Since this is basically a function definition without a return type, it becomes clear how type inference can be useful in determining the expression's type to declare the proper delegate and verify that type is valid.

Other Implications

When Option Explicit is turned off, a local variable can be used in code without an explicit declaration. As with the case described previously, the variable is assumed to be of type Object and all calls are late-bound. It is worth noting that type inference does not occur on implicitly defined variables, so the behavior has not changed at all. The following code shows an example:

Sub SwapTwoInts(ByVal x As Integer, ByVal y As Integer)
    z = x
    x = y
    y = z
End Sub

Using Visual Basic 2005, it was possible to declare a temporary variable implicitly in the Immediate Window while debugging by assigning some expression to an identifier not defined in the current scope. The result was that a variable of type Object was created and assigned the value specified, much as if this assignment was made in code with Option Explicit Off. In Visual Basic 2008, the behavior I described for the Immediate Window and the behavior I described with Option Explicit Off will be the same as they were with Visual Basic 2005, thus type inference will not take place. If I enter the following two commands into the Immediate Window, the following result will be displayed, showing an Object variable given the Integer value 12:

intVar = 12
?intVar
12 {Integer}

Go To Type Definition

When types are explicitly given in a variable definition, the Visual Studio® "Go To Definition" action can easily be invoked from the context menu of the type identifier to determine where the type is defined. In the following case, the user can click on IEnumerable and the result will be IEnumerable(Of T) in the Object Browser:

Dim customers As IEnumerable(Of Customer) = GetCustomers()

With the new language features discussed here, I expect that it will more frequently be the case that the type is difficult to discern at a glance. Because of type inference, types need not be explicit, and with the new language constructs supporting LINQ, it can be quite obscure. The following variable is of type IEnumerable(Of T) as well, but it's much less obvious here, and there is no place to call Go To Definition to navigate to its type:

Dim customers = From cust In _
                    (From c In db.Customers Select c) _
                Where cust.FirstName = "Danae" _
                Select cust.LastName, cust.Address

For Each c In customers
...

This issue motivated the introduction of the Go To Type Definition command in Visual Studio 2008. You can also call the command from the context menu, so you can find the definition of a variable's type even if only the variable identifier exists in code. You can execute the Go To Type Definition command on the definition of a variable or a reference. In the previous code snippet, you could simply call Go To Type Definition on the customers variable in the definition or in the For loop, and you would be brought to the Object Browser with IEnumerable(Of T) selected (see Figure 5). Were the type explicitly defined in code, you would instead navigate to that location.

Figure 5 Go To Type Definition Command in a For Loop

Figure 5** Go To Type Definition Command in a For Loop **

Enabling and Disabling Type Inference

One of the major implications of type inference support is that some code compiled in a previous version of Visual Basic may not compile in a project where type inference is enabled. This is why upgraded projects will have type inference turned off by default. The main area where such build errors could occur is in code that uses late binding.

Using earlier versions of Visual Basic, late binding occurs when Option Strict is turned off, and a variable with no explicit type is initialized to an expression. The variable is given type Object, and any member or method calls on that variable will be bound at run time:

' Late Binding with Visual Basic 2005
Dim x = 5
x = New List(Of Integer)
x.Sort()
x = New Queue(Of Integer)
x.Enqueue(6)

With type inference, such calls would result in compiler errors. Instead of giving the variable type Object, the compiler gives the variable a type to match the expression to which it is initialized. Late binding is still possible with type inference enabled, but Object must be given explicitly as the type:

' Late Binding with Visual Basic 2008
Dim x As Object = 5
x = New List(Of Integer)
x.Sort()
x = New Queue(Of Integer)
x.Enqueue(6)

There are other times you'll want to turn type inference off as well. Fortunately there is a compiler option not unlike Option Strict or Option Explicit, called Option Infer, that lets you turn type inference on or off. It's placed at the beginning of a code file like all compiler options in Visual Basic:

Option Infer On    ' This enables type inference for the file

Option Infer Off   ' This disables type inference, so declarations 
                   ' with no type specified will use Object

If Option Infer is not specified for a particular code file, the project-level setting is used. The project-level default setting for Option Infer can be set on the Compile tab of the Project Designer. The Project Designer can be accessed from the Project menu or My Project node of Solution Explorer.

Option Infer is On by default in new Visual Basic 2008 projects and Off by default for upgraded projects in order to avoid breaking pre-existing code. File-level settings override this option for specific files.

While there are plenty of advantages to using type inference, as you will see when you begin using LINQ and other technologies that benefit from it, you have the ability to specify when it will be invoked and when it will not, providing you with maximum flexibility.

Send your questions and comments for Bill to instinct@microsoft.com.

Bill Horst is a Software Design Engineer in Test with the Microsoft Visual Basic Team. He has done much of the testing for LINQ Queries and also tests Visual Basic debugging.