Chapter 20. Advanced Language Features

Back in 2008, the .NET Framework 3.5 introduced revolutionary technologies such as LINQ. Because of its complex infrastructure, all .NET languages (especially VB and C#) required new keywords, syntaxes, and constructs to interact with LINQ but that could be successfully used in lots of other scenarios. Visual Basic 2010 introduced even more features to make the coding experience better and Visual Basic 2012 not only continues to support those language features but also applies some fixes to small bugs. Most of the language features discussed in this chapter are important for the comprehension of the next chapters, so I recommend you pay particular attention to the topics presented here.

Local Type Inference

Local type inference is a language feature that allows you to omit specifying the data type of a local variable even if Option Strict is set to On. The Visual Basic compiler can deduce (infer) the most appropriate data type depending on the variable’s usage. The easiest way to understand and hopefully appreciate local type inference is to provide a code example. Consider the following code and pay attention to the comments:

Sub Main()
    'The compiler infers String
    Dim oneString = "Hello Visual Basic 2012!"

    'The compiler infers Integer
    Dim oneInt = 324

    'The compiler infers Double
    Dim oneDbl = 123.456

    'The compiler infers Boolean

    Dim oneBool = True
End Sub

As you can see, the code doesn’t specify the type for all variables because the compiler can infer the most appropriate data type according to the usage of a variable. To ensure that the VB compiler inferred the correct type, pass the mouse pointer over the variable declaration to retrieve information via a useful tooltip, as shown in Figure 20.1.

Image

Figure 20.1. A tooltip indicates which type the Visual Basic compiler inferred to local variables.


Behind the Scenes of Local Type Inference

Types’ inference is determined via the dominant type algorithm. This was described perfectly (and is still valid) in the Visual Basic 10.0 language specifications document, available at http://www.microsoft.com/en-us/download/details.aspx?id=15039.


The local type inference also works with custom types, as demonstrated by the following example:

Dim p As New Person With {.FirstName = "Alessandro",
                          .LastName = "Del Sole"}
'The compiler infers Person
Dim onePerson = p

You can also use local type inference within loops or in any other circumstance you like:

'The compiler infers System.Diagnostic.Process
For Each proc In Process.GetProcesses
    Console.WriteLine(proc.ProcessName)
Next

Option Infer Directive

To enable or disable local type inference, the Visual Basic grammar provides the Option Infer directive. Option Infer On enables inference, whereas Option Infer Off disables it. You do not need to explicitly provide an Option Infer directive because it is offered at the project level by Visual Studio. By default, Option Infer is On. If you want to change the default settings for the current project, open My Project and then switch to the Compile tab. There you can find the Visual Basic compiler options, including Option Infer. If you instead want to change settings for each new project, select the Options command from the Tools menu. When the Options dialog box appears, move to the Projects and Solutions tab and select the VB defaults item, as shown in Figure 20.2.

Image

Figure 20.2. Changing the default behavior for Option Infer.

You then need to add an Option Infer On directive if you want to switch back to local type inference.

Local Type Inference Scope

The word local in the local type inference definition has a special meaning. Local type inference works only with local variables defined within code blocks and does not work with class-level declarations. For example, consider the following code:

Class Person

    Property LastName As String
    Property FirstName As String

    Function FullName() As String
        'Local variable: local type inference works
        Dim completeName = Me.LastName & " " & Me.FirstName
        Return completeName
    End Function
End Class

Local type inference affects the completeName local variable, which is enclosed within a method. Now consider the following code:

'Both Option Strict and Option Infer are On
Class Person
    'Local type inference does not work with
    'class level variables. An error will be
    'thrown.
    Private completeName

The preceding code will not be compiled because local type inference does not affect class-level declarations; therefore, the Visual Basic compiler throws an error if Option Strict is On. If Option Strict is Off, the completeName class-level variable will be considered of type Object but still won’t be affected by local type inference. So be aware of this possible situation. The conclusion is that you always need to explicitly provide a type for class-level variables, whereas you can omit the specification with local variables.


Why Local Type Inference?

If you are an old-school developer, you probably will be surprised and perhaps unhappy by local type inference because you always wrote your code the most strongly typed possible. You will not be obliged to declare types by taking advantage of local type inference except when you need to generate anonymous types, which are discussed later in this chapter. I always use (and suggest) the local type inference because it’s straightforward and avoids the need of worrying about types while still providing type safety, especially with different kinds of query result when working with LINQ. I often use this feature in the rest of the book.


Array Literals

The array literals feature works like the local type inference but is specific to arrays. It was first introduced in Visual Basic 2010. Consider this array of strings declaration as you would write it in Visual Basic 2008:

Dim anArrayOfStrings() As String = {"One", "Two", "Three"}

In Visual Basic 2010 and 2012 you can write it as follows:

'The compiler infers String()
Dim anArrayOfStrings = {"One", "Two", "Three"}

According to the preceding code, you are still required to place only a couple of parentheses, but you can omit the type that is correctly inferred by the compiler as you can easily verify by passing the mouse pointer over the variable declaration. Of course, array literals work with value types, too, as shown here:

'The compiler infers Double
Dim anArrayOfDouble = {1.23, 2.34, 3.45}
'The compiler infers Integer
Dim anArrayOfInteger = {4, 3, 2, 1}

Array literals also support mixed arrays. For example, the following array is inferred as an array of Object:

'Does not work with Option Strict On
Dim mixedArray = {1.23, "One point Twentythree"}

The preceding code will not be compiled if Option Strict is On, and the compiler will show a message saying that the type cannot be inferred, which is a situation that can be resolved explicitly by assigning the type to the array. You could therefore explicitly declare the array as Dim mixedArray() As Object, but you need to be careful in this because mixed arrays could lead to errors.

Bug Fix: Return Type in Array Literals

Visual Basic 2012 fixes a bug related to the return type in array literals. Strictly related to the previous code snippet about mixed arrays, the following code in Visual Basic 2010 would result in an error because the type returned by the Return statement would be considered as Object():

Function oneMethod(i As Integer) As Integer()
  If i = 0 Then Return {}
  Return {1, 2, i}
End Function

This has been fixed in Visual Basic 2012 and the compiler is now capable of inferring the appropriate type (Integer in this case).

Multidimensional and Jagged Arrays

Array literals also affect multidimensional and jagged arrays. The following line of code shows how you can declare a multidimensional array of integers by taking advantage of array literals:

Dim multiIntArray = {{4, 3}, {2, 1}}

In this case, you do not need to add parentheses. The Visual Basic compiler infers the type as follows:

Dim multiIntArray(,) As Integer = {{4, 3}, {2, 1}}

Figure 20.3 shows how you can check the inferred type by passing the mouse pointer over the declaration, getting a descriptive tooltip.

Image

Figure 20.3. Type inference for a multidimensional array.

Array literals work similarly on a jagged array. For example, you can write a jagged array of strings as follows:

Dim jaggedStringArray = {({"One", "Two"}),
                         ({"Three", "Four"})}

This is the same as writing:

Dim jaggedStringArray()() As String = {({"One", "Two"}),
                                      ({"Three", "Four"})}

And the same as for multidimensional arrays in which the code editor can provide help on type inference, as shown in Figure 20.4.

Image

Figure 20.4. Type inference for a jagged array.

Array literals can help in writing more elegant and shorter code.

Extension Methods

Extension methods are a feature that Visual Basic 2012 inherits from its predecessors. As for other features discussed in this chapter, their main purpose is being used with LINQ, although they can also be useful in hundreds of scenarios. Extension methods are special methods that can extend the data type they are applied to. The most important thing is that you can extend existing types even if you do not have the source code and without the need to rebuild class libraries that expose types you go to extend—and this is important. For example, you can extend .NET built-in types, as you see in this section, although you do not have .NET source code.

We now discuss extension methods in two different perspectives: learning to use existing extension methods exposed by .NET built-in types and implementing and exporting custom extension methods. The first code example retrieves the list of processes running on the system and makes use of an extension method named ToList:

Dim processList = Process.GetProcesses.ToList

ToList converts an array or an IEnumerable collection into a strongly typed List(Of T), in this case into a List(Of Process) (notice how the assignment works with local type inference). Extension methods are easily recognizable within IntelliSense because they are characterized by the usual method icon plus a blue down arrow, as shown in Figure 20.5.

Image

Figure 20.5. Recognizing extension methods within IntelliSense.

They are also recognizable because the method definition is marked as <Extension> as you can see from the descriptive tooltip shown in Figure 20.5, but this will also be discussed in creating custom methods. The next example uses the AsEnumerable method for converting an array of Process into an IEnumerable(Of Process):

Dim processEnumerable As IEnumerable(Of Process) =
                         Process.GetProcesses.AsEnumerable

Extension methods can execute hundreds of tasks, so it is not easy to provide a general summarization, especially because they can be customized according to your needs. At a higher level, .NET built-in extension methods accomplish three main objectives: converting types into other types, data filtering, and parsing. The most common built-in .NET extension methods are provided by the System.Linq.Enumerable class and are summarized in Table 20.1.

Table 20.1. Built-in Extension Methods

Image
Image
Image

Arguments as Lambdas

In most cases you use lambda expressions as arguments for extension methods. Lambda expressions are discussed later in this book; therefore, examples where lambdas are not used are provided.


In the next part of this book, which is dedicated to data access with LINQ, you see how extension methods are used for filtering, ordering, and parsing data. The following code snippet shows an example of filtering data using the Where extension method:

'A real app example would use
'a lambda expression instead of a delegate
Dim filteredProcessList = Process.GetProcesses.
    Where(AddressOf EvaluateProcess).ToList

Private Function EvaluateProcess(ByVal p As Process) As Boolean
    If p.ProcessName.ToLowerInvariant.StartsWith("e") Then Return True
End Function

The preceding code adds Process objects to a list only if the process name starts with the letter e. The evaluation is performed through a delegate; although in this chapter you learn how to accomplish this using the lambda expression. Table 20.1 cannot be exhaustive because the .NET Framework offers other extension methods specific to some development areas that are eventually discussed in the appropriate chapters. IntelliSense provides help about extension methods not covered here. By reading Table 20.1, you can also understand that extension methods from System.Linq.Enumerable work on or return results from a sequence of elements. This notion is important because you use such methods against a number of different collections (that is, sequences of elements of a particular type), especially when working with LINQ.


Extension Methods Behavior

Although extension methods behave as instance methods, the Visual Basic compiler translates them into static methods. This is because extension methods are defined within modules (or static classes if created in Visual C#).


Coding Custom Extension Methods

One of the most interesting things when talking about extension methods is that you can create your custom extensions. This provides great power and flexibility to development because you can extend existing types with new functionalities, even if you do not have the source code for the type you want to extend. There are a set of rules and best practices to follow in coding custom extension methods; the first considerations are the following:

• In Visual Basic, extension methods can be defined only within modules because they are considered as shared methods by the compiler.

• Only Function and Sub methods can be coded as extensions. Properties and other members cannot work as extensions.

• Methods must be decorated with the System.Runtime.CompilerServices.Extension attribute. Decorating modules with the same attribute is also legal but not required.

• Extension methods can be overloaded.

• Extension methods can extend reference types, value types, delegates, arrays, interfaces, and generic parameters but cannot extend System.Object to avoid late binding problems.

• They must receive at least an argument. The first argument is always the type that the extension method goes to extend.

For example, imagine you want to provide a custom extension method that converts an IEnumerable(Of T) into an ObservableCollection(Of T). The ObservableCollection is a special collection exposed by the System.Collections.ObjectModel namespace from the WindowsBase.dll assembly, which is usually used in WPF applications. (You need to add a reference to WindowsBase.dll.) The code in Listing 20.1 shows how this can be implemented.

Listing 20.1. Implementing Custom Extension Methods


Imports System.Runtime.CompilerServices
Imports System.Collections.ObjectModel

<Extension()> Module Extensions
    <Extension()> Function ToObservableCollection(Of T) _
                           (ByVal List As IEnumerable(Of T)) _
                           As ObservableCollection(Of T)
        Try
            Return New ObservableCollection(Of T)(List)
        Catch ex As Exception
            Throw
        End Try
    End Function

End Module


The code in Listing 20.1 is simple. Because the ObservableCollection is generic, the ToObservableCollection extension method is also generic and goes to extend the generic IEnumerable type, which is the method argument. The constructor of ObservableCollection provides an overload that accepts an IEnumerable to populate the new collection and then returns an instance of the collection starting from the IEnumerable data. Using the new method is straightforward:

Dim processCollection = Process.GetProcesses.ToObservableCollection

Now suppose you want to extend the String type to provide an extension method that can check whether a string is a valid email address. In such a situation the best check can be performed using regular expressions. The following code shows how you can implement this extension method:

'Requires an Imports System.Text.RegularExpressions statement
<Extension()> Function IsValidEMail(ByVal EMailAddress As String) _
              As Boolean
    Dim validateMail As String = _
    "^([w-.]+)@(([[0-9]{1,3}.[0-9]{1,3}.)" & _
    "|(([w-]+.)+))([a-zA-z]{2,4}|[0-9]{1,3})(]?)$"

    Return Regex.IsMatch(EMailAddress, _
                          validateMail)

End Function

The goal is not to focus on the comparison pattern via regular expressions, which is complex. Just notice how the result of the comparison (Regex.IsMatch) is returned by the method that extends String because such type is the first (and only) argument in the method. You can then use the method as follows:

Dim email As String = "[email protected]"
If email.IsValidEMail Then
    Console.WriteLine("Valid address")
Else
    Console.WriteLine("Invalid address")
End If

You may remember that extension methods are shared methods but behave as instance members; this is the reason the new method is available on the email instance and not on the String type.

Overloading Extension Methods

Extension methods support the overloading technique and follow general rules already described in Chapter 7, “Class Fundamentals,” especially that overloads cannot differ only because of their return types but must differ in their signatures.

Exporting Extension Methods

You can create libraries of custom extension methods and make them reusable from other languages. This could be useful if you need to offer your extension methods to other applications written in different programming languages. To accomplish this, you need to be aware of a couple of things. First, the module defining extensions must be explicitly marked as Public, and the same is true for methods. Second, you need to write a public sealed class with an empty private constructor because the Common Runtime Language (CLR) provides access to extension methods through this class, to grant interoperability between languages. Listing 20.2 shows a complete example.

Listing 20.2. Building an Extension Methods Library


Imports System.Runtime.CompilerServices
Imports System.Collections.ObjectModel
Imports System.Text.RegularExpressions

<Extension()> Public Module Extensions
    <Extension()> Public Function ToObservableCollection(Of T) _
                           (ByVal List As IEnumerable(Of T)) _
                           As ObservableCollection(Of T)
        Try
            Return New ObservableCollection(Of T)(List)
        Catch ex As Exception
            Throw
        End Try
    End Function

    <Extension()> Public Function IsValidEMail(ByVal EMailAddress As String) _
                  As Boolean
        Dim validateMail As String = _
        "^([w-.]+)@(([[0-9]{1,3}.[0-9]{1,3}.)" & _
        "|(([w-]+.)+))([a-zA-z]{2,4}|[0-9]{1,3})(]?)$"

        Return Regex.IsMatch(EMailAddress, _
                              validateMail)

    End Function
End Module

Public NotInheritable Class MyCustomExtensions

    Private Sub New()

    End Sub
End Class


Creating a public sealed class is necessary because modules are a specific feature of Visual Basic; therefore, such a class is the bridge between our code and other languages. By compiling the code shown in Listing 20.2 as a class library, .NET languages can take advantage of your extension methods.


Testing Custom Extension Libraries

If you want to be sure that class libraries exposing custom extension methods work correctly, create a new Visual C# project (a VB one is good as well) and add a reference to the new assembly. Then write code that invokes extended types and check via IntelliSense whether your custom methods are effectively available.



Exporting Extension Methods Tips

Exporting extension methods requires a little bit of attention. For example, extending types in which you do not own the source code can be dangerous because it may lead to conflicts if, in the future, the original author adds extensions with the same name. Instead consider encapsulating extensions within specific namespaces. Microsoft created a document containing a series of best practices that can be found at the following address: http://msdn.microsoft.com/en-us/library/bb384936(VS.110).aspx


Anonymous Types

As their name implies, anonymous types are .NET objects that have no previously defined type in the .NET Framework or your code and can be generated on-the-fly. They were first introduced with .NET Framework 3.5, and their main purpose is collecting data from LINQ queries. You prefer named types to anonymous types outside particular LINQ scenarios; however, it’s important to understand how anonymous types work. Declaring an anonymous type is straightforward, as shown in the following code snippet:

Dim anonymous = New With {.FirstName = "Alessandro",
                          .LastName = "Del Sole",
                          .Email = "",
                          .Age = 32}

As you can see, no name for the new type is specified, and a new instance is created just invoking the New With statement. Creating an anonymous type takes advantage of two previously described features, object initializers and local type inference. Object initializers are necessary because anonymous types must be generated in one line, so they do need such a particular feature. Local type inference is fundamental because you have no other way for declaring a new type as an anonymous type, meaning that only the compiler can do it via local type inference. This is the reason declaring an anonymous type cannot be accomplished using the As clause. For example, the following code throws an error and will not be compiled:

'Throws an error: "the keyword does not name a type"
Dim anonymous As New With {.FirstName = "Alessandro",
                          .LastName = "Del Sole",
                          .Age = 35}

Local type inference is also necessary for another reason. As you can see, you can assign but not declare properties when declaring an anonymous type. (FirstName, LastName, Age, and Email are all properties for the new anonymous type that are both implemented and assigned.) Therefore, the compiler needs a way to understand the type of a property and then implement one for you, and this is only possible due to the local type inference. In the preceding example, for the FirstName, LastName, and Email properties, the compiler infers the String type, whereas for the Age property it infers the Integer type. When you have an anonymous type, you can use it like any other .NET type. The following code provides an example:

'Property assignment
anonymous.Email = "[email protected]"
'Property reading
Console.WriteLine("{0} {1}, of age: {2}",
                  anonymous.FirstName,
                  anonymous.LastName,
                  anonymous.Age.ToString)

As previously mentioned, you can work with an anonymous type like with any other .NET type. The difference is that anonymous types do not have names. Such types can also implement read-only properties. This can be accomplished using the Key keyword with a property name, as demonstrated here:

'The Age property is read-only and can
'be assigned only when creating an instance
Dim anonymousWithReadOnly = New With {.FirstName = "Alessandro",
                                      .LastName = "Del Sole",
                                      Key .Age = 35}

In this example, the Age property is treated as read-only and thus can be assigned only when creating an instance of the anonymous type. You probably wonder why anonymous types can be useful. You get more practical examples in Part IV, “Data Access with ADO.NET and LINQ.”

Relaxed Delegates

When you code methods that are pointed to by delegates, your methods must respect the delegate’s signature. An exception to this rule is when your method receives arguments that are not effectively used and therefore can be omitted. Such a feature is known as relaxed delegates. The simplest example to help you understand relaxed delegates is to create a WPF application. After you’ve created your application, drag a Button control from the toolbox onto the main window’s surface. Double-click the new button to activate the code editor so that Visual Studio generates an event handler stub for you and type the following code:

Private Sub Button1_Click(ByVal sender As Object,
                               ByVal e As RoutedEventArgs) _
                               Handles Button1.Click
    MessageBox.Show("It works!")
End Sub

As you can see, the method body shows a text message but does not make use of both sender and e arguments received by the event handler (which is a method pointed by a delegate). Because of this, Visual Basic allows an exception to the method signature rule, and therefore the preceding method can be rewritten as follows:

'Relaxed delegate
Private Sub Button1_Click() Handles Button1.Click
    MessageBox.Show("It works! - relaxed version")
End Sub

The code still works correctly because the compiler can identify the preceding method as a relaxed delegate. This feature can be useful especially in enhancing code readability.

Lambda Expressions

Lambda expressions have existed in .NET development since Visual Basic 2008. Because of their flexibility, they are one of the most important additions to .NET programming languages. The main purpose of lambda expressions, as for other language features, is related to LINQ, as you see in the next chapters. They can also be successfully used in many programming scenarios. Lambda expressions in Visual Basic are anonymous methods that can be generated on-the-fly within a line of code and can replace the use of delegates. The easiest explanation of lambdas is that you can use a lambda wherever you need a delegate.


Understanding Lambda Expressions

Lambda expressions are powerful, but they are probably not easy to understand at first. Because of this, several steps of explanations are provided before describing their common usage, although this might seem unnecessary.


You create lambda expressions using the Function keyword. When used for lambda expressions, this keyword returns a System.Func(Of T, TResult) (with overloads) delegate that encapsulates a method that receives one or more arguments of type T and returns a result of type TResult. System.Func is defined within the System.Core.dll assembly and can accept as many T arguments for as many parameters that are required by the anonymous method. The last argument of a System.Func type is always the return type of a lambda. For example, the following line of code creates a lambda expression that accepts two Double values and returns another Double constituted by the multiplication of the first two numbers:

Dim f As Func(Of Double, Double, Double) = Function(x, y) x * y

As you can see, the Function keyword does not take any method name. It just receives two arguments, and the result is implemented after the last parenthesis. Such a lambda expression returns a System.Func(Of Double, Double, Double) in which the first two doubles correspond to the lambda’s arguments, whereas the third one corresponds to the lambda’s result type. You can then invoke the obtained delegate to perform a calculation, as in the following line:

'Returns 12
Console.WriteLine(f(3, 4))

Of course, this is not the only way to invoke the result of a lambda expression, but it is an important starting point. The code provides a lambda instead of declaring an explicit delegate. Now consider the following code that rewrites the previously shown lambda expression:

Function Multiply(ByVal x As Double, ByVal y As Double) As Double
    Return x * y
End Function
Dim f As New Func(Of Double, Double, Double)(AddressOf Multiply)

'Returns 12
Console.WriteLine(f(3, 4))

As you can see, this second code explicitly creates a method that performs the required calculation that is then passed to the constructor of the System.Func. Invoking the delegate can then produce the same result. The difference is that using a lambda expression brought major elegance and dynamicity to our code. System.Func can receive up to 16 arguments; independently from how many arguments you need, remember that the last one is always the return value. Another common scenario is a lambda expression that evaluates an expression and returns a Boolean value. To demonstrate this, we can recall the IsValidEMail extension method that was described in the “Extension Methods” section to construct complex code. Listing 20.3 shows how you can invoke extension methods for a lambda expression to evaluate whether a string is a valid email address, getting back True or False as a result.

Listing 20.3. Complex Coding with Lambda Expressions


Module TestLambda

    Sub ComplexEvaluation()
        Dim checkString As Func(Of String, Boolean) = Function(s) s.IsValidEMail
        Console.WriteLine(checkString("[email protected]"))
    End Sub
End Module

<Extension()> Module Extensions
    <Extension()> Public Function IsValidEMail(ByVal EMailAddress As String) _
              As Boolean
        Dim validateMail As String = _
        "^([w-.]+)@(([[0-9]{1,3}.[0-9]{1,3}.)" & _
        "|(([w-]+.)+))([a-zA-z]{2,4}|[0-9]{1,3})(]?)$"

        Return Regex.IsMatch(EMailAddress, _
                              validateMail)
    End Function
End Module


If you look at Listing 20.3, you notice that the checkString delegate takes a String to evaluate and returns Boolean. Such an evaluation is performed invoking the IsValidEMail extension method.


Ok, But Why Lambdas?

You might wonder why you need lambda expressions instead of invoking methods. The reason is code robustness offered by delegates, as you will remember from Chapter 15, “Delegates and Events.” Therefore if you decide to use delegates, using lambda expressions is a good idea while it becomes a necessity if you access data with LINQ.


You often use lambda expressions as arguments for extension methods. The following code shows how you can order the names of running processes on your machine, including only names starting with the letter e:

Dim processes = Process.GetProcesses.
                OrderBy(Function(p) p.ProcessName).
                Where(Function(p) p.ProcessName.ToLowerInvariant.
                StartsWith("e"))

The OrderBy extension method receives a lambda expression as an argument that takes an object of type System.Diagnostics.Process and orders the collection by the process name, whereas the Where extension method still receives a lambda as an argument pointing to the same Process instance and that returns True if the process name starts with the letter e. To get a complete idea of how the lambda works, the best way is rewriting code without using the lambda. The following code demonstrates this concept; and the first lambda remains to provide an idea of how code can be improved using such a feature:

'An explicit method that evaluates the expression
Private Function EvaluateProcess(ByVal p As Process) As Boolean
    If p.ProcessName.ToLowerInvariant.StartsWith("e") Then
        Return True
    Else
        Return False
    End If
End Function
    Dim processes = Process.GetProcesses.
                    OrderBy(Function(p) p.ProcessName).
                    Where(AddressOf EvaluateProcess)

As you can see, avoiding the usage of lambda expressions requires you to implement a method that respects the signature of the System.Func delegate and that performs the required evaluations. Such a method is then pointed via the AddressOf keyword. You can easily understand how lambda expressions facilitate writing code and make code clearer, especially if you compare the OrderBy method that still gets a lambda expression. For the sake of completeness, it’s important to understand that lambda expressions improve the coding experience, but the Visual Basic compiler still translates them the old-fashioned way, as explained later in the “Lexical Closures” section. All the examples provided until now take advantage of the local type inference feature and leave to the VB compiler the work of inferring the appropriate types. The next section discusses this characteristic.

Type Inference and Lambda Expressions

At a higher level, lambda expressions fully support local type inference so that the Visual Basic compiler can decide for you the appropriate data type. For lambdas, there is something more to say. Type inference is determined by how you write your code. Consider the first lambda expression at the beginning of this section:

Dim f As Func(Of Double, Double, Double) = Function(x, y) x * y

In the preceding code, local type inference affects both arguments and the result of the Function statement. The compiler can infer Double to the x and y parameters and therefore can determine Double as the result type. This is possible only because we explicitly provided types in the delegate declaration—that is, Func(Of Double, Double, Double). Because a local type inference is determined by the compiler using the dominant algorithm, something must be explicitly typed. For a better understanding, rewrite the preceding code as follows:

'The compiler infers Object
Dim f = Function(x, y) x * y

In this case because no type is specified anywhere, the compiler infers Object for the f variable, but in this special case it also throws an exception because operands are not supported by an Object. Thus, the code will not be compiled if Option Strict is On. If you set Option Strict Off, you can take advantage of late binding. In such a scenario both the result and the arguments will be treated as Object at compile time, but at runtime the CLR can infer the appropriate type depending on the argument received by the expression. The other scenario is when the type result is omitted but arguments’ types are provided. The following code demonstrates this:

Dim f = Function(x As Double, y As Double) x * y

In this case the result type for the f variable is not specified, but arguments have been explicitly typed so that the compiler can infer the correct result type.

Multiline Lambdas

With multiline lambdas, you have the ability to write complete anonymous delegates within a line of code, as demonstrated in the following snippet:

Console.WriteLine("Enter a number:")
Dim number = CDbl(Console.ReadLine)

Dim result = Function(n As Double)
                 If n < 0 Then
                     Return 0
                 Else
                     Return n + 1
                 End If
             End Function

Console.WriteLine(result(number))

In this particular case, the compiler can infer the System.Func(Of Double, Double) result type because the n argument is of type Double. Within the method body, you can perform required evaluations, and you can also explicitly specify the return type (take a look at the first lambda example) to get control over the System.Func result. Another example is for multiline lambdas without variable declarations, as in the following code:

Dim processes = Process.GetProcesses.
                Where(Function(p)
                          Try
                              'Returns True
                              p.ProcessName.ToLowerInvariant.
                              StartsWith("e")
                          Catch ex As Exception
                              Return False
                          End Try
                      End Function)

This code performs the same operations described in the “Lambda Expressions” section, but now you have the ability to write more complex code—for example, if you need to provide error handling infrastructures as previously shown.

Sub Lambdas

So far you have seen lambda expressions that were represented only by functions that could return a value and that were realized via the Function keyword. You can also use Sub lambdas that C# developers know as anonymous methods. This feature lets you use the Sub keyword instead of the Function one so that you can write lambda expressions that do not return a value. The following code demonstrates this:

Dim collection As New List(Of String) From {"Alessandro",
                                      "Del Sole",
                                      [email protected]"}

collection.ForEach(Sub(element) Console.WriteLine(element))

The preceding code iterates a List(Of String) collection and sends to the console window the result of the iteration. A Sub lambda is used because here no return value is required.


Array.Foreach and List(Of T).Foreach

The System.Array and the System.Collections.Generic.List(Of T) classes offer a ForEach method that allows performing loops similarly to the For..Each statement described in Chapter 4, “Data Types and Expressions.” The difference is that you can take advantage of lambda expressions and eventually of delegates to iterate elements.


Consider that trying to replace Sub with Function causes an error. (That makes sense because Console.WriteLine does not return values while Function does.) Like Function, arguments’ types within Sub can be inferred by the compiler. In this case the element is of type String because it represents a single element in a List(Of String) collection. You can use Sub lambdas each time a System.Action(Of T) is required, opposite to the System.Func(Of T, T) required by Function. System.Action(Of T) is a delegate that represents a method accepting just one argument and that returns no value. Sub lambdas can also be implemented as multiline lambdas. The following code shows a multiline implementation of the previous code, where a simple validation is performed onto every string in the collection:

' "collection" has the same previous implementation
collection.ForEach(Sub(element)
                       Try
                          If String.IsNullOrEmpty(element) = False Then
                             Console.WriteLine(element)
                          Else
                             Console.
                             WriteLine("Cannot print empty strings")
                          End If
                       Catch ex As Exception

                       End Try
                   End Sub)

In this way you can also implement complex expressions, although they do not return a value.


Lambda Expressions and Object Lifetime

When methods end their job, local variables get out of scope and therefore are subject to garbage collection. By the way, lambda expressions within methods hold references to local variables unless you explicitly release resources related to the lambda. Consider this when planning objects’ lifetime management.


Lexical Closures

To provide support for lambda expressions, the Visual Basic compiler implements a background feature known as lexical closures. Before going into the explanation, remember that you will not use closures in your code because they are typically generated for compiler use only. However, it’s important to know what they are and what they do. Lexical closures allow access to the same class-level variable to multiple functions and procedures. A code example provides a better explanation. Consider the following code, in which the Divide method takes advantage of a lambda expression to calculate the division between two numbers:

Class ClosureDemo

    Sub Divide(ByVal value As Double)
        Dim x = value
        Dim calculate = Function(y As Double) x / y
        Dim result = calculate(10)
    End Sub
End Class

Because both the Divide method and its lambda expression have access to the x local variable, the compiler internally rewrites the preceding code in a more logical way that looks like the following:

Class _Closure$__1

    Public x As Double

    Function _Lambda$__1(ByVal y As Double) As Double
        Return x * y
    End Function
End Class

Class ClosureDemo

    Sub Divide(ByVal value As Double)
       Dim closureVariable_A_8 As New _
           _Closure$__1
       _Closure$__1.closureVariable_A_8 = value

       Dim calculate As Func(Of Double, Double) _
       = AddressOf _Closure$__1._Lambda$__1
       Dim result = calculate(10)

    End Sub
End Class

Identifiers are not easy to understand, but they are generated by the compiler that is the only one responsible for their handling. The lexical closure feature creates a new public class with a public field related to the variable having common access. It also generated a separate method for performing the division that is explicitly accessed as a delegate (and here you will remember that lambdas can be used every time you need a delegate) from the Divide method. In conclusion, lexical closures provide a way for a logical organization of the code that provides behind-the-scenes support for lambda expressions but, as stated at the beginning of this section, they are exclusively the responsibility of the Visual Basic compiler.

Ternary If Operator

The ternary If operator is used with lambda expressions and allows evaluating conditions on-the-fly. With this operator, you can evaluate a condition and return the desired value either in case the condition is True or if it is False. Imagine you have a Person class exposing both FirstName and LastName string properties and that you want to first verify that an instance of the Person class is not Nothing and, subsequently, that its LastName properties are initialized. The following code shows how you can accomplish the first task (see comments):

Sub EvaluatePerson(ByVal p As Person)
    'Check if p (a Person instance) is Nothing
    'If it is Nothing, returns False else True
    'The result is returned as a delegate
    Dim checkIfNull = If(p Is Nothing, False, True)

    'If False, p is Nothing, therefore
    'throws an exception
    If checkIfNull = False Then
        Throw New ArgumentNullException("testPerson")
    End If

End Sub

As you can see, the If operator receives three arguments: The first one is the condition to evaluate; the second is the result to return if the condition is True; the third is the result to return if the condition is False. In this specific example, if the Person instance is null the code returns False; otherwise, it returns True. The result of this code is assigned to a Boolean variable (checkIfNull) that contains the result of the evaluation. If the result is False, the code throws an ArgumentNullException. You could write the preceding code in a simpler way, as follows:

If p Is Nothing Then
    'do something
Else
    Throw New ArgumentNullException
End If

The difference is that, with the ternary operator, you can perform inline evaluations also with lambda expressions, and this scenario is particularly useful when working with LINQ queries. Now it’s time to check whether the LastName property was initialized. The following code snippet shows an example that returns a String instead of a Boolean value:

Dim executeTest = If(String.IsNullOrEmpty(p.LastName) = True,
                  "LastName property is empty",
                  "LastName property is initialized")

The explanation is simple: If the LastName property is an empty string or a null string, the code returns a message saying that the property is empty. Otherwise, it returns a message saying that the property has been correctly initialized. You could rewrite the code in the classic fashion as follows:

If String.IsNullOrEmpty(p.LastName) = True Then
    'LastName property is empty
Else
    'LastName property is initialized
End If

The following lines show how you can test the preceding code:

'Throws an ArgumentNullException
EvaluatePerson(Nothing)
'A message says that the LastName property is initialized
EvaluatePerson(New Person With {.LastName = "Del Sole"})

Generic Variance

The concept of generic variance was introduced in Visual Basic 2010 and is divided into two areas: covariance and contra variance. This concept is related to inheritance versus generics and generic collections; a couple of examples are provided next for a better explanation.

Covariance

Covariance enables you to assign strongly typed collections (such as List) of derived classes to IEnumerable collections of abstract classes. The code in Listing 20.4 shows how covariance works.

Listing 20.4. Covariance in Visual Basic 2012


Module Covariance

    Sub Main()

        'Using collection initializers
        Dim stringsCollection As New List(Of String) _
                              From {"Understanding ", "covariance ", "in VB 2010"}

        'This code is now legal
        Dim variance As IEnumerable(Of Object) = stringsCollection

        For Each s In variance
            Console.WriteLine(s)
        Next

        Console.ReadLine()
    End Sub
End Module


If you examine Listing 20.4, you see that the variance variable is generic of type IEnumerable(Of Object) and receives the assignment of a generic List(Of String) content, in which Object is the base class of String. Until Visual Basic 2008, this code was illegal and would therefore throw a compile exception. In Visual Basic 2010 and 2012, this code is legal but works only with IEnumerable(Of T) collections. If you try to replace IEnumerable(Of Object) with List(Of Object), the compiler still throws an error suggesting that you use an IEnumerable. By the way, you assigned a collection of String to a collection of Object, and this is how covariance works. The For..Each loop correctly recognizes items in the IEnumerable as String and therefore produces the following, simple output:

Understanding
covariance
in VB 2010

Contra Variance

Contra variance works the opposite of covariance: From a derived class, we can take advantage of an abstract class or of a base class. To understand how contra variance works, the best example is to create a client application in which two events of the same control are handled by the same event handler. For this, create a new Windows Presentation Foundation application and write the following XAML code to define a simple Button control:

<Button Content="Button" Height="50" Name="Button1" Width="150" />


Creating WPF Applications

If you are not familiar with WPF applications, you notice one important thing when creating such projects: The designer provides a graphical editor and the code editor for the XAML code (which is XML-styled). You can write the previous snippet within the XAML code editor or drag a Button control from the toolbox onto the Window, which is not a problem. You can also notice, in Solution Explorer, the presence of the code-behind file that has an .xaml.vb extension. There you can write the code shown next. Chapter 28, “Creating WPF Applications,” discusses WPF applications in detail.


Like other controls, the Button control exposes several events. For example, let’s consider the MouseDoubleClick event and the KeyUp event and decide that it would be a good idea to handle both events writing a unique event handler. To accomplish this, we can take advantage of contra variance. Consider the following code, which explicitly declares handlers for events:

Public Sub New()

    ' This call is required by the Windows Form Designer.
    InitializeComponent()

    ' Explicitly specify handlers for events
    AddHandler Button1.KeyUp, AddressOf CommonHandler
    AddHandler Button1.MouseDoubleClick, AddressOf CommonHandler
End Sub

Both events point to the same delegate, which is implemented as follows:

Private Sub CommonHandler(ByVal sender As Object,
                          ByVal e As EventArgs)
    MessageBox.Show("You did it!")
End Sub

The KeyUp event should be handled by a delegate that receives a System.Windows.Input.KeyEventArgs argument, whereas the MouseDoubleClick should be handled by a delegate that receives a System.Windows.Input.MouseButtonEventArgs argument. Because both objects inherit from System.EventArgs, we can provide a unique delegate that receives an argument of such type and that can handle both events. If you now try to run the application, you see that the message box is correctly shown if you either double-click the button or press a key when the button has the focus. The advantage of contra variance is that we can use abstract classes to handle the behavior of derived classes.

Summary

In this chapter, you got the most out of some advanced language features that provide both special support for the LINQ technology and improvements to your coding experience. Local type inference enables developers to avoid specifying types in local variables assignment because the Visual Basic compiler automatically provides the most appropriate one. Array literals extend local type inference to arrays. Extension methods allow extending existing objects, even if you do not own the source code (such as in case of the .NET Framework) with custom methods. Anonymous types enable you to generate on-the-fly no-name types that you often use within LINQ queries. Relaxed delegates provide the ability to write code smarter and faster because you are authorized to not respect delegates’ signatures if you do not use arguments. Lambda expressions strengthen your code by introducing anonymous delegates that can be generated on-the-fly, improving your code quality and efficiency. Sub lambdas can be used to handle anonymous delegates that do not return a value. Generic covariance and contra variance provide further control over generic IEnumerable collections when you work with inheritance.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.217.254.118