Type Relationships in Query Operations (Visual Basic)
Variables used in Language-Integrated Query (LINQ) query operations are strongly typed and must be compatible with each other. Strong typing is used in the data source, in the query itself, and in the query execution. The following illustration identifies terms used to describe a LINQ query. For more information about the parts of a query, see Basic Query Operations (Visual Basic).
The type of the range variable in the query must be compatible with the type of the elements in the data source. The type of the query variable must be compatible with the sequence element defined in the Select
clause. Finally, the type of the sequence elements also must be compatible with the type of the loop control variable that is used in the For Each
statement that executes the query. This strong typing facilitates identification of type errors at compile time.
Visual Basic makes strong typing convenient by implementing local type inference, also known as implicit typing. That feature is used in the previous example, and you will see it used throughout the LINQ samples and documentation. In Visual Basic, local type inference is accomplished simply by using a Dim
statement without an As
clause. In the following example, city
is strongly typed as a string.
Dim city = "Seattle"
Note
Local type inference works only when Option Infer
is set to On
. For more information, see Option Infer Statement.
However, even if you use local type inference in a query, the same type relationships are present among the variables in the data source, the query variable, and the query execution loop. It is useful to have a basic understanding of these type relationships when you are writing LINQ queries, or working with the samples and code examples in the documentation.
You may need to specify an explicit type for a range variable that does not match the type returned from the data source. You can specify the type of the range variable by using an As
clause. However, this results in an error if the conversion is a narrowing conversion and Option Strict
is set to On
. Therefore, we recommend that you perform the conversion on the values retrieved from the data source. You can convert the values from the data source to the explicit range variable type by using the Cast method. You can also cast the values selected in the Select
clause to an explicit type that is different from the type of the range variable. These points are illustrated in the following code.
Dim numbers1() As Integer = {1, 2, 4, 16, 32, 64}
Dim numbers2() As Double = {5.0#, 10.0#, 15.0#}
' This code does not result in an error.
Dim numberQuery1 = From n As Integer In numbers1 Where n > 5
' This code results in an error with Option Strict set to On. The type Double
' cannot be implicitly cast as type Integer.
Dim numberQuery2 = From n As Integer In numbers2 Where n > 5
' This code casts the values in the data source to type Integer. The type of
' the range variable is Integer.
Dim numberQuery3 = From n In numbers2.Cast(Of Integer)() Where n > 5
' This code returns the value of the range variable converted to Integer. The type of
' the range variable is Double.
Dim numberQuery4 = From n In numbers2 Where n > 5 Select CInt(n)
Queries That Return Entire Elements of the Source Data
The following example shows a LINQ query operation that returns a sequence of elements selected from the source data. The source, names
, contains an array of strings, and the query output is a sequence containing strings that start with the letter M.
Dim names = {"John", "Rick", "Maggie", "Mary"}
Dim mNames = From name In names
Where name.IndexOf("M") = 0
Select name
For Each nm In mNames
Console.WriteLine(nm)
Next
This is equivalent to the following code, but is much shorter and easier to write. Reliance on local type inference in queries is the preferred style in Visual Basic.
Dim names2 = {"John", "Rick", "Maggie", "Mary"}
Dim mNames2 As IEnumerable(Of String) =
From name As String In names
Where name.IndexOf("M") = 0
Select name
For Each nm As String In mNames
Console.WriteLine(nm)
Next
The following relationships exist in both of the previous code examples, whether the types are determined implicitly or explicitly.
The type of the elements in the data source,
names
, is the type of the range variable,name
, in the query.The type of the object that is selected,
name
, determines the type of the query variable,mNames
. Herename
is a string, so the query variable is IEnumerable(Of String) in Visual Basic.The query defined in
mNames
is executed in theFor Each
loop. The loop iterates over the result of executing the query. BecausemNames
, when it is executed, will return a sequence of strings, the loop iteration variable,nm
, also is a string.
Queries That Return One Field from Selected Elements
The following example shows a LINQ to SQL query operation that returns a sequence containing only one part of each element selected from the data source. The query takes a collection of Customer
objects as its data source and projects only the Name
property in the result. Because the customer name is a string, the query produces a sequence of strings as output.
' Method GetTable returns a table of Customer objects.
Dim customers = db.GetTable(Of Customer)()
Dim custNames = From cust In customers
Where cust.City = "London"
Select cust.Name
For Each custName In custNames
Console.WriteLine(custName)
Next
The relationships between variables are like those in the simpler example.
The type of the elements in the data source,
customers
, is the type of the range variable,cust
, in the query. In this example, that type isCustomer
.The
Select
statement returns theName
property of eachCustomer
object instead of the whole object. BecauseName
is a string, the query variable,custNames
, will again be IEnumerable(Of String), not ofCustomer
.Because
custNames
represents a sequence of strings, theFor Each
loop's iteration variable,custName
, must be a string.
Without local type inference, the previous example would be more cumbersome to write and to understand, as the following example shows.
' Method GetTable returns a table of Customer objects.
Dim customers As Table(Of Customer) = db.GetTable(Of Customer)()
Dim custNames As IEnumerable(Of String) =
From cust As Customer In customers
Where cust.City = "London"
Select cust.Name
For Each custName As String In custNames
Console.WriteLine(custName)
Next
Queries That Require Anonymous Types
The following example shows a more complex situation. In the previous example, it was inconvenient to specify types for all the variables explicitly. In this example, it is impossible. Instead of selecting entire Customer
elements from the data source, or a single field from each element, the Select
clause in this query returns two properties of the original Customer
object: Name
and City
. In response to the Select
clause, the compiler defines an anonymous type that contains those two properties. The result of executing nameCityQuery
in the For Each
loop is a collection of instances of the new anonymous type. Because the anonymous type has no usable name, you cannot specify the type of nameCityQuery
or custInfo
explicitly. That is, with an anonymous type, you have no type name to use in place of String
in IEnumerable(Of String)
. For more information, see Anonymous Types.
' Method GetTable returns a table of Customer objects.
Dim customers = db.GetTable(Of Customer)()
Dim nameCityQuery = From cust In customers
Where cust.City = "London"
Select cust.Name, cust.City
For Each custInfo In nameCityQuery
Console.WriteLine(custInfo.Name)
Next
Although it is not possible to specify types for all the variables in the previous example, the relationships remain the same.
The type of the elements in the data source is again the type of the range variable in the query. In this example,
cust
is an instance ofCustomer
.Because the
Select
statement produces an anonymous type, the query variable,nameCityQuery
, must be implicitly typed as an anonymous type. An anonymous type has no usable name, and therefore cannot be specified explicitly.The type of the iteration variable in the
For Each
loop is the anonymous type created in step 2. Because the type has no usable name, the type of the loop iteration variable must be determined implicitly.