Visual Basic 2008 führte Lambda-Funktionen ein - auch unter dem Namen anonyme Methoden bekannt - und mit ihnen eine Erleichterung im Umgang mit Delegaten: Lambdas sind Methoden, die keinen Methodennamen besitzen; stattdessen können lediglich ihre Programmadressen in Delegat-Variablen gespeichert oder diese direkt an Methoden übergeben werden, die Delegaten einer bestimmten Signatur als Parameter erwarten. Bis Visual Basic 2008 war der Umgang mit Lambdas eingeschränkt, da diese einerseits nur einzeilig sein durften und andererseits grundsätzlich Funktionsergebnisse zurückliefern mussten.
Die ForEach-Methode der generischen Liste zeigt einfach und plausibel den Umgang mit Lambdas: Geht es darum, möglichst schnell die Elemente einer generischen Liste zu verarbeiten, eignet sich die ForEach-Methode im Zusammenhang mit einer Lambda-Funktion besonders, da sie - im Gegensatz zum herkömmlichen For Each-Konstrukt - intern nicht den Umweg über GetEnumerator zum Aufzählen aller Elemente der Liste nehmen muss, sondern direkt auf die Listenelemente zugreifen kann.
Um alle Elemente einer Liste beispielsweise im Debug-Fenster mithilfe einer Lambda-Funktion auszugeben, kann man sich folgendem Code bedienen:
Sub LambdaDemo()
Dim namenListe As New List(Of String) From {"Peter", "Klaus", "Tanja", "Sonja"}
'Anstelle eines Delegaten:
Dim writeToDebugDelegate As New Action(Of String)(AddressOf WriteToDebug)
namenListe.ForEach(writeToDebugDelegate)
'Schreiben wir das hier direkt:
namenListe.ForEach(Function(item) WriteToDebug(item))
End Sub
Shared Function WriteToDebug(ByVal text As String) As Boolean
Debug.Print(text)
Return True
End Function
Action(of String) ist in diesem Fall der Delegattyp, den die ForEach-Methode als Delegatvariable erwartet. Und hier sind Lambdas eine Erleichterung, weil eine Lambda-Funktion der ForEach-Methode auch direkt als Parameter übergeben werden kann.
Bis Visual Basic 2008 war man allerdings, wie schon gesagt, bei Lambdas auf Methoden mit Rückgabeparameter beschränkt. Aus diesem Grund gab es bis zu dieser VB-Version auch nicht die Möglichkeit, zum Beispiel ein Konstrukt wie Debug.Print direkt in der Lambda-Funktion anzugeben, da Debug.Print eben eine Sub, also eine Methode ohne Rückgabewert darstellt.
Ab Visual Basic 2010 ist das anders. Hier ist dieses Konstrukt
namenListe.ForEach(Sub(item) Debug.Print(item))
ebenso erlaubt, wie mehrzeilige Lambdas:
namenListe.ForEach(Sub(item)
'Hier können jetzt mehrere Codezeilen platziert werden
Debug.Print(item)
End Sub)
Der Einsatz von Lambdas wird Visual Basic-Programme in Zukunft deutlich anders ausschauen lassen. Lambdas haben hier das Potential, einerseits dem Entwickler Arbeit abzunehmen, weil er für viele Zwecke nicht mehr den Umweg über Delegaten nehmen muss, und das spart Codezeilen und Coding-Zeit. Andererseits wirkt der Code auch aufgeräumter, da Code an der Stelle steht, wo er passiert.
Das hat ab dem .NET Framework 4.0 umsomehr Bedeutung, da Delegaten hier verstärkt zum Einsatz kommen, wenn die Codeausführung parallelisiert, also automatisch auf mehrere Prozessoren oder Prozessor-Cores verteilt werden muss, wie das folgende Beispiel zeigt:
Parallel.ForEach(namenListe, Sub(item)
'Elemente parallel verarbeiten.
'ACHTUNG: Das passiert durch die Gleichzeitigkeit
'nicht mehr sequentiell sondern durcheinander!
Debug.Print(item)
End Sub)
In diesem Beispiel muss übrigens der Namespace System.Threading.Tasks importiert werden.
In der Praxis kann der Umgang mit Lambdas dann folgender Maßen ausschauen. In dem folgenden Beispiel geht es um die Validierung einer Eingabe in einem Steuerelemente, die, wenn sie fehlschlägt, eine Exception zurückliefert aufgrund derer dann ein Tooltip mit dem entsprechenden Fehlermeldungstext direkt neben dem Steuerelement ausgegeben werden soll, das die Fehlermeldung erzeugt hat:
Protected Overrides Sub OnValidating(ByVal e As System.ComponentModel.CancelEventArgs)
MyBase.OnValidating(e)
If e.Cancel Then
Return
End If
'ValidateInput überprüft, ob die Eingabe i.O. geht, und liefert
'bei einem Fehler eine Exception zurück, die letzten Endes
'auch den Fehlertext ergibt.
Dim valRes = ValidateInput(myValueControl.Value.ToString)
If valRes IsNot Nothing Then
e.Cancel = True
'Bei Bedarf, kann hier ein Warnton ausgegeben werden.
If myBeepOnFailedValidation Then
Beep()
End If
'Die Fehlermeldung soll in einem Tooltip neben
'dem Steuerelement für eine gewisse Zeit zu sehen sein.
Dim tt As New ToolTip()
'Steuerelement malen wir selbst.
tt.OwnerDraw = True
'Soll sich Ausblenden.
tt.UseFading = True
tt.UseAnimation = True
'Dank Mehrzeiligen Lambdas, können wir den Drawing-Handler Code
'direkt dem Draw-Ereignis übergeben.
AddHandler tt.Draw, Sub(sender As Object, de As DrawToolTipEventArgs)
Dim pen As New Pen(Brushes.Black, 1)
'Point-Array clonen und Koordinaten anpassen
Dim path As New GraphicsPath()
Dim p1, p2 As PointF
p1 = PointF.Empty
'Die Koordinaten des Rahmen des
'Tooltipps stehen in einer List(Of PointF),
'die wir mit der ForEach-Methode, und wieder
'mit einem mehrzeiligen Lambda durchiterieren.
myToolTipShapeCoordinates.ForEach(
Sub(item)
'Koordinaten des Tooltips an
'die des Steuerelements anpassen:
Dim x = item.X
Dim y = item.Y
If x = -1 Then
x = de.Bounds.X + de.Bounds.Width - 1
Else
x += de.Bounds.X
End If
If y = -1 Then
y = de.Bounds.Y + de.Bounds.Height - 1
Else
y += de.Bounds.Y
End If
p1 = p2
p2 = New PointF(x, y)
If p1 <> Point.Empty Then
path.AddLine(p1, p2)
End If
End Sub)
'Zum Malen auf AntiAlias stellen: sieht "weicher" aus.
de.Graphics.SmoothingMode = SmoothingMode.AntiAlias
path.CloseFigure()
'Hintergrund, Umrandung und Text des Tooltips malen:
de.Graphics.FillPath(New SolidBrush(Color.AliceBlue), path)
de.Graphics.DrawPath(Pens.Black, path)
de.Graphics.DrawString(de.ToolTipText, New Font(FontFamily.GenericSansSerif,
8, FontStyle.Regular),
Brushes.Black,
New RectangleF(de.Bounds.X + 15, de.Bounds.Y + 5,
de.Bounds.Width - 15, de.Bounds.Height - 5))
End Sub
'Tooltip ausgeben. Das erst triggert den obenstehenden Code,
'da jetzt erst das Draw-Ereignis des Tooltips ausgelöst wird,
'dem dieser Code mit AddHandler zugeordnet wurde.
tt.Show("Die Eingabe hatte einen Fehler:" & vbNewLine & vbNewLine &
valRes.Message, Me, Me.Width - 10, Me.Height \ 2, Me.ExceptionBalloonDuration)
End If
End Sub