In This Chapter
• Delegates
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 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.
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
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.
You then need to add an Option Infer On
directive if you want to switch back to local type inference.
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.
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.
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).
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.
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.
Array literals can help in writing more elegant and shorter code.
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.
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.
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.
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#).
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.
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.
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.
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.
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
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.”
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 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.
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.
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.
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.
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.
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.
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"})
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 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.
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 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.
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.