Visual Basic 9.0 Features Corresponding to C 3.0


Visual Basic 9.0 Features Corresponding to C# 3.0

Most of the new Visual Basic 9.0 features have a C# 3.0 equivalent. For the sake of conciseness, this section will concentrate on the specific Visual Basic 9.0 syntax. The considerations about implications and possible uses of new features are the same that we offered in Chapter 2 for the corresponding C# 3.0 features.

Local Type Inference

The local type inference feature is also called implicitly typed local variables. It allows the definition of variables by inferring types from the assigned expression. At first sight, a Visual Basic developer might think that this is the same behavior that is obtained with Option Strict Off. In fact, you still get a strongly typed variable if you assign a value in the declaration statement. The example in Listing 3-3 shows you this syntax; the comments indicate the effective type of the variable declared.

Listing 3-3: Local type inference

image from book
  Dim x = 2.3      ' Double Dim y = x        ' Double Dim r = x / y    ' Double Dim s = "sample" ' String Dim l = s.Length ' Integer Dim w = d        ' Decimal Dim o            ' Object – allowed only with Option Strict Off 
image from book

We see the same behavior here that we have seen in C#. The syntax for Visual Basic simply omits the As type part of the declaration. The declaration of o is not valid if you enable Option Strict On. Note that even with Option Strict Off active, all variables types are those inferred by the initialization expression. In fact, that setting would compile the example even with Visual Basic 8.0, but all variables would be of the Object type, boxing the value assigned in the initialization expression.

Note 

We recommend that you use Option Strict On unless you have a good reason to avoid it. For example, when you access a Component Object Model (COM) object through interop without having a primary interop assembly, the late-binding behavior of Option Strict Off could be useful to call methods implemented only through the IDispatch interface that are not exposed in the type library of the COM object.

The use of Option Strict On can help you avoid some possible errors. For example, consider the code in Listing 3-4.

Listing 3-4: Changed behavior from Visual Basic 8.0 to Visual Basic 9.0

image from book
  Option Strict Off Module LocalTypeInference     Sub BeCareful()         Dim a = 10         a = "Hello"         Console.WriteLine(a)     End Sub     '... End Module 
image from book

In Visual Basic 8.0, the a variable is of type Object; therefore, we can always change the assigned value type because it is eventually boxed. Executing the BeCareful method in Visual Basic 8.0, we display the string Hello. In Visual Basic 9.0, using Option Strict Off, we get an exception when trying to assign a String (that does not contain a number) to an Integer variable:

 Unhandled Exception: System.InvalidCastException: Conversion from string "Hello" to type 'Integer' is not valid. ---> System.FormatException: Input string was not in a correct format

If you use Option Strict On, you get a compile error for the code in Listing 3-4. Visual Basic 8.0 does not accept the a declaration; Visual Basic 9.0 does not like the Hello string to be assigned to an integer variable. Be careful when you migrate existing Visual Basic code to LINQ if you have always used Option Strict Off.

Important 

You can disable local type inference by specifying Option Infer Off. By default, a new Visual Basic 9.0 project uses Option Infer On. To avoid possible issues in code migration, use Option Infer Off when you are migrating code from a previous version of Visual Basic.

Extension Methods

Extension methods can be defined in Visual Basic 9.0 with a technique that produces results like those obtained in C#. We will concentrate our attention only on syntax differences here. The code in Listing 3-5 uses traditional method declarations and calls to convert a decimal value into a string formatted with a specific culture.

Listing 3-5: Standard method declaration and use

image from book
  Module Demo     Sub DemoTraditional()         Dim x As Decimal = 1234.568         Console.WriteLine(FormattedIT(x))         Console.WriteLine(FormattedUS(x))     End Sub End Module Public Class TraditionalMethods     Shared Function FormattedIT(ByVal d As Decimal) As String         Return String.Format(formatIT, "{0:#,0.00}", d)     End Function     Shared Function FormattedUS(ByVal d As Decimal) As String         Return String.Format(formatUS, "{0:#,0.00}", d)     End Function     Shared formatUS As CultureInfo = New CultureInfo("en-US")     Shared formatIT As CultureInfo = New CultureInfo("it-IT") End Class 
image from book

As with C#, we can change this Visual Basic code to extend the decimal type. Instead of adding a keyword (as in C#, which uses this before the first argument), in Visual Basic we decorate both the extension method and the containing class with the attribute System.Runtime.CompilerServices.Extension.

Note 

The Extension attribute is automatically generated by the C# compiler when it encounters the “this” keyword before the first argument in a method declaration. Visual Basic assigns this task to the programmer.

To use a shorter attribute name, we can add the Imports System.Runtime.CompilerServices statement, as you can see in Listing 3-6.

Listing 3-6: Extension method declaration

image from book
  Imports System.Runtime.CompilerServices <Extension()> _ Public Module ExtensionMethods     <Extension()> _     Public Function FormattedIT(ByVal d As Decimal) As String         Return String.Format(formatIT, "{0:#,0.00}", d)     End Function     <Extension()> _     Public Function FormattedUS(ByVal d As Decimal) As String         Return String.Format(formatUS, "{0:#,0.00}", d)     End Function     Private formatUS As CultureInfo = New CultureInfo("en-US")     Private formatIT As CultureInfo = New CultureInfo("it-IT") End Module 
image from book

An extension method must be defined in a Module, and both the method and the containing module must be decorated with the Extension attribute. The first parameter type is the type that the method extends. Usually, extension methods and containing modules are public because they can be (and normally are) called even from outside the assembly in which they are declared.

Note 

At this point, the compiled code for the ExtensionMethods module contains metadata, as we would have defined a class with both MustInherit and NotInheritable keywords, which is a syntax that is not allowed by the compiler. When decompiling the code with ILDASM (Intermediate Language Disassembler) or Reflector, you have to interpret this condition as the equivalent of a static class in C#. ILDASM is a tool that is part of the .NET Framework SDK. Reflector is a free decompiler that supports several languages, including C# and Visual Basic, and is available at http://www.aisto.com/roeder/dotnet/.

Using an extension method from Visual Basic code requires that the class containing extension methods be used as a parameter of an Imports statement. This is different from C#, which requires a using statement for the containing namespace and not of the specific class. In Listing 3-7, you can see the call of the extension methods we declared in the previous sample. We use Imports ExtensionVB.ExtensionMethods because the containing namespace is ExtensionVB (it is either the name of our assembly or the name of the root namespace), and ExtensionMethods is the name of the class containing our extension methods.

Listing 3-7: Extension method use

image from book
  Imports ExtensionVB.ExtensionMethods Module Demo     Sub DemoExtension()         Dim x As Decimal = 1234.568         Console.WriteLine(x.FormattedIT())         Console.WriteLine(x.FormattedUS())     End Sub End Module 
image from book

Almost all the considerations about extension methods that we covered in Chapter 2 are valid here, with the only exceptions being the syntax differences that we have highlighted.

Object Initialization Expressions

Visual Basic 9.0 offers a new syntax to initialize multiple members of the same type instance. The With syntax does the same work, but the object initializer in Visual Basic 9.0 allows the initialization of multiple members in a single expression. This functionality is necessary to initialize anonymous types, as we will see later.

We will consider the class shown in Listing 3-8 in our examples.

Listing 3-8: Sample class for object initializers

image from book
  Public Class Customer     Public Age As Integer     Public Name As String     Public Country As String     Public Sub New(ByVal name As String, ByVal age As Integer)         Me.Age = age         Me.Name = name     End Sub     ' ... End Class 
image from book

If we want to initialize Country but not Age on a new instance for Customer, in Visual Basic 8.0, we can write the code based on the With statement shown in Listing 3-9.

Listing 3-9: Initialization using With statement

image from book
  Dim customer As New Customer With customer     .Name = "Marco"     .Country = "Italy" End With 
image from book

The new object initializer syntax of Visual Basic 9.0 allows initialization in a different form, as we can see in Listing 3-10.

Listing 3-10: Object initializer syntax

image from book
  Dim customer As Customer customer = New Customer With {.Name = "Marco", .Country = "Italy"} 
image from book

The object initializer syntax expects the keyword With after the object creation, followed by brackets containing a list of members to initialize. Each member is assigned by specifying its name prefixed by a dot, followed by an equals sign (=) and then the initialization expression. Multiple members are separated by a comma (,).

If you are a C# developer, you should be aware that splitting an expression on multiple lines in Visual Basic always requires the line continuation notation, which is a special character (an underscore) at the end of each line. You can see how to use them in Listing 3-11.

Listing 3-11: Object initializer syntax on multiple lines

image from book
  Dim customer As Customer = _     New Customer With { _         .Name = "Marco", _         .Country = "Italy"} 
image from book

The use of line continuations can negate the benefits of object initializers. We generally prefer to use the new object initializer syntax rather than the traditional With statement in cases in which we can write everything on a single line. For example, we can leverage local type inference to write the code in Listing 3-12.

Listing 3-12: Object initializer syntax and local type inference

image from book
  Dim customer = New Customer With {.Name = "Marco", .Country = "Italy"} 
image from book

Object initializers can use constants or other expressions for the assigned values. Moreover, you can use a nondefault constructor followed by an object initializer. The example in Listing 3-13 illustrates these concepts.

Listing 3-13: Object initializers using expressions

image from book
  Dim c1 = New Customer With {.Name = "Marco", .Country = "Italy"} Dim c2 = New Customer("Paolo", 21) With {.Country = "Italy"} Dim c3 = New Customer With {.Name = "Paolo", .Age = 21, .Country = "Italy"} Dim c4 = New Customer With {.Name = c1.Name, .Country = c2.Country, .Age = c2.Age} 
image from book

Because an object initializer is an expression, it can be nested. Listing 3-14 shows a possible use.

Listing 3-14: Nested object initializers

image from book
  ' In Point and Rectangle classes, we collapsed parts of implementation Public Class Point     Private _x, _y As Integer     Public Property X ... ' Integer - implementation collapsed     Public Property Y ... ' Integer - implementation collapsed End Class Public Class Rectangle     Private _tl, _br As Point     Public Property TL() ... ' Point - implementation collapsed     Public Property BR() ... ' Point - implementation collapsed End Class ' Possible code inside a method     Dim r = New Rectangle With { _                 .TL = New Point With {.X = 0, .Y = 1}, _                 .BR = New Point With {.X = 2, .Y = 3} _             } 
image from book

Anonymous Types

An anonymous type is a type that is declared without an identifier. Anonymous types in Visual Basic 9.0 are aligned with the corresponding C# 3.0 features. You can use object initializers without specifying the class that will be created with the New operator. When you do this, a new class (an anonymous type) is created. Consider Listing 3-15.

Listing 3-15: Anonymous type definition

image from book
  Dim c1 As New Customer With {.Name = "Marco"} Dim c2 = New Customer With {.Name = "Paolo"} Dim c3 = New With {.Name = "Tom", .Age = 31} Dim c4 = New With {c2.Name, c2.Age} Dim c5 = New With {c1.Name, c1.Country} Dim c6 = New With {c1.Country, c1.Name} 
image from book

The variables c1 and c2 are of the Customer type, but the type of variables c3, c4, c5, and c6 cannot be inferred simply by reading the printed code. The local type inference should infer the variable type from the assigned expression, but we do not have an explicit type after the New keyword for these expressions. That kind of object initializer generates a new class.

The generated class has a public property and an underlying private field for each argument contained in the initializer; the property’s name and type are inferred from the object initializer. That class is the same for all possible anonymous types that have the same names and types in the same order for their properties. We can see the type names used and generated with this code:

 Console.WriteLine("c1 is {0}", c1.GetType()) Console.WriteLine("c2 is {0}", c2.GetType()) Console.WriteLine("c3 is {0}", c3.GetType()) Console.WriteLine("c4 is {0}", c4.GetType()) Console.WriteLine("c5 is {0}", c5.GetType()) Console.WriteLine("c6 is {0}", c6.GetType())

The output generated is the following:

 c1 is AnonymousTypes.Customer c2 is AnonymousTypes.Customer c3 is VB$AnonymousType_0`2[System.String,System.Int32] c4 is VB$AnonymousType_0`2[System.String,System.Int32] c5 is VB$AnonymousType_1`2[System.String,System.String] c6 is VB$AnonymousType_2`2[System.String,System.String]

The anonymous type name cannot be referenced by the code (because you do not know the generated name), but it can be queried on an object instance. The variables c3 and c4 are of the same anonymous type because they have the same fields and properties. Even if c5 and c6 have the same properties (type and name), they are in a different order-that is enough for the compiler to create two different anonymous types.

If you use the syntax for a collection initializer, you can create an array of anonymous types, as shown in Listing 3-16.

Listing 3-16: Array of anonymous types

image from book
  Dim ca = {New With {.Name = "Marco", .Country = "Italy"}, _           New With {.Name = "Tom", .Country = "USA"}, _           New With {.Name = "Paolo", .Country = "Italy"} _          } Dim cs = {New With {.Name = "Marco", .Sports = {"Tennis", "Spinning"}}, _           New With {.Name = "Tom", .Sports = {"Rugby", "Squash", "Baseball"}}, _           New With {.Name = "Paolo", .Sports = {"Skateboard", "Windsurf"}} _          } 
image from book

The first array, ca, is made by instances of an anonymous type that have two members of type String. The second array, cs, is an array of a different anonymous type, which is formed by a string Name and an array of strings named Sports.

Query Expressions

Visual Basic 9.0 supports the concept of query expressions (a syntax similar to the SQL language used to manipulate data) just as C# 3.0 does. Preliminary documentation of Visual Basic 9.0 talks about query comprehensions to identify this language-integrated syntax for queries. A detailed explanation of all the keywords valid in a query expression will be shown in Chapter 4, “LINQ Syntax Fundamentals.” This section illustrates the main syntax differences in query expressions between C# and Visual Basic 9.0. Some of the specific details will be more clear after you read Chapter 4.

A LINQ query can be written in Visual Basic using the From Where Select pattern, as shown in Listing 3-17.

Listing 3-17: Simple LINQ query

image from book
  Dim customers() = { _     New With {.Name = "Marco", .Discount = 4.5}, _     New With {.Name = "Paolo", .Discount = 3.0}, _     New With {.Name = "Tom", .Discount = 3.5} _ } Dim query = _     From c In customers _     Where c.Discount > 3 _     Select New With {c.Name, .Perc = c.Discount / 100} 
image from book

The Where clause is optional, but From and Select are mandatory. If the Where clause is present, the predicate used as the Where condition is transformed into a lambda expression (as shown in the next section) by the compiler. Another nested function is generated for the projection (the code that follows the Select keyword).

In the case of an Order By clause, the order of keywords is different than in C# 3.0. For example, consider the C# 3.0 code shown in Listing 3-18.

Listing 3-18: Order By in C# 3.0

image from book
  var query =     from    c in customers     where   c.Discount > 3     orderby perc     select  new {c.Name, Perc = c.Discount / 100) 
image from book

The code in Listing 3-18 can be written in Visual Basic 9.0, as shown in Listing 3-19.

Listing 3-19: Order By in Visual Basic 9.0

image from book
  Dim query = _     From c In customers _     Where c.Discount > 3 _     Select r = New With {c.Name, .Perc = c.Discount / 100} _     Order By r.Perc 
image from book

Note that Order By precedes select in C# 3.0, but it is immediately after Select in Visual Basic 9.0.

Lambda Expressions

C# 3.0 allows you to explicitly write a lambda expression using this syntax:

 (c) => c.Country == "USA" ( a, b ) => a + b

The corresponding syntax in Visual Basic 9.0 is based on the keyword Function, as you can see in Listing 3-20.

Listing 3-20: Lambda expressions in Visual Basic 9.0

image from book
  Function(c) c.Country = "USA" Function( a, b ) a + b 
image from book

Using lambda expressions, you can write more complex query expressions, as shown in Listing 3-21.

Listing 3-21: Use of lambda expressions in query expressions

image from book
  Dim customers As List(Of Customer) = New List(Of Customer) Dim query = customers.FindAll( Function(c) c.Country = "USA" ); 
image from book

Closures

When a query expression contains a delegate as a parameter, in C# 3.0 we use a lambda expression to define the delegate in a shorter and easier way. We can use lambda expressions even in Visual Basic 9.0, and the compiler produces a similar result by creating closures, which are delegates that capture their surrounding context and pass them to the underlying method call. For example, with the query in Listing 3-22, the compiler generates two lambda expressions that represent the delegate to be passed to the Select and Where functions.

Listing 3-22: Closures with query expressions

image from book
  Dim maxPlayers = 2 Dim players = _     From customer In customers _     Where customer.Sports.Count > maxPlayers _     Select customer.Name 
image from book

The two lambda expressions generate two corresponding closures. These closures have the same scope as the method that contains the players variable assignment, which uses the lambda expressions. This setup is necessary to access the maxPlayers variable. The generated code is equivalent to the following:

 Dim maxPlayers = 2 Dim players = _     Enumerable.Select(         Enumerable.Where( customers,             Function(customer) customer.Sports.Count > maxPlayers ),             Function(customer) customer.Name );




Introducing Microsoft LINQ
Introducing MicrosoftВ® LINQ
ISBN: 0735623910
EAN: 2147483647
Year: 2007
Pages: 78

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net