vb@rchiv
VB Classic
VB.NET
ADO.NET
VBA
C#

https://www.vbarchiv.net
Rubrik: .NET   |   VB-Versionen: VB200821.05.08
Neuerungen in Visual Basic 2008 - Von Local Type Inference bis zu LINQ

Dieser Workshop soll Ihnen einen Überblick über die Neuerungen von Visual Basic 2008 geben. Als Entwicklungsumgebung wird die Express Edition von Visual Basic 2008 verwendet.

Autor:  Ralf EhlertBewertung:  Views:  15.828 

Local Type Inference (lokaler Typrückschluss)
Bei diesem Feature brauchen Sie keine As-Klausel angeben, sondern der Compiler weist der Variablen einen Typen zu. Die Verwendung davon ist sehr einprägsam:

Dim text = "Hallo VB2008!"

Was passiert hier? Der Compiler ermittelt den Typen des Ausdrucks auf der rechten Seite und weist diesen Typen der Variable zu. Damit dies funktioniert, müssen zwei Bedingungen erfüllt sein:

  • Option Strict muss aktiviert sein, andernfalls wird die Variable als Object definiert und
  • Option Infer muss ebenfalls aktiviert sein, was die Standardeinstellung ist.

Wofür braucht man das? Type Inference erspart in VB kaum Tipparbeit, da in VB über As New Typangabe und Objekterzeugung kombiniert werden kann. Es ist aber notwendig, um anonyme Typen nutzen zu können, wie Sie beispielsweise in LINQ-Szenarien vorkommen (dazu später mehr).

Da Type Inference nur innerhalb von Methoden erlaubt ist, hält sich die Fehlverwendung in Grenzen. Unter VB würde ich dieses Feature nur bei LINQ-Abfragen verwenden.

Object Initializers (Objektinitialisierer)
Hiermit können komplexe Objekte ohne den Aufruf eines Konstruktors initialisiert werden. Gehen wir davon aus, dass wir folgende Klasse haben:

Public Class Person
  Private nameValue As String
  Public Property Name() As String
    Get
      Return nameValue
    End Get
    Set(ByVal value As String)
      nameValue = value
    End Set
  End Property
 
  Private alterValue As Integer
  Public Property Alter() As Integer
    Get
      Return alterValue
    End Get
    Set(ByVal value As Integer)
      alterValue = value
    End Set
  End Property
End Class

Um jetzt eine Instanz von dieser Klasse zu erzeugen und zu initialisieren, dient z. B. folgender Code:

Dim paul As New Person()
With paul
  .Name = "Paul"
  .Alter = 25
End With

Mit VB2008 kann man das jetzt zu einer Anweisung verbinden:

Dim paul As New Person With {.Name = "Paul", .Alter = 25}

Dies kann man auch mit einem parametrisierten Konstruktor verbinden. Sie können also einen Teil der Daten mit einem Konstruktor initialisieren und den Rest über Objektinitialisierer.

Anonymous Types (Anonyme Typen)
Dieses Feature erlaubt es, Klasseninstanzen zu erzeugen, ohne eine Klassendefinition zu erstellen. Der Compiler erzeugt die Klassendefinition anhand der angegebenen Eigenschaften:

Dim hans = New With {Key .Name = "Hans", .Alter = 19}

Wie Sie sehen, arbeiten bei anonymen Typen Type Inference und Objektinitialisierer zusammen. Das Schlüsselwort Key vor einer Eigenschaft zeichnet diese als Haupteigenschaft (key property) aus. Anhand von den Haupteigenschaften werden Instanzen eines anonymen Typen verglichen und nur diese gehen in den Hashalgorithmus ein, den der Compiler generiert. Weiterhin ist eine Haupteigenschaft schreibgeschützt.

Anonyme Typen sind nur innerhalb einer Methode zulässig und können nicht als Parameter u. ä. verwendet werden. Bei LINQ-Abfragen werden diese verwendet, um nur eine bestimmte Auswahl der Ergebnismenge zurückzugeben. Außerhalb von LINQ sehe ich diese kaum als sinnvoll an.

Extension Methods (Erweiterungsmethoden)
Erweiterungsmethoden sind der Grundstein für die LINQ-Funktionalität, welche im .NET Framework 3.5 enthalten sind. Sie erlauben es, bestehende Typen um Methoden zu erweitern (auch Typen, von denen man nicht erben kann). Das folgende Beispiel soll als Erklärungsgrundlage dienen:

Imports System.Runtime.CompilerServices
 
Public Module Extensions
  <Extension()> _
  Public Function IsGerade(ByVal zahl As Integer) As Boolean
    Return zahl Mod 2 = 0
  End Function
End Module

Eine Erweiterungsmethode ist durch das Attribut Extension() gekennzeichnet, welches sich im Namensraum System.Runtime.CompilerServices befindet. Der erste Parameter der Erweiterungsmethode gibt den Typen an, der erweitert werden soll. Danach folgende Parameter haben keine besondere Bedeutung. Der Aufruf erfolgt wie bei jeder anderen Methode:

Dim zahl = 5
Dim res = zahl.IsGerade()

Erweiterungsmethoden sind nur syntaktischer Zucker. Der Compiler formt diese um:

Extensions.IsGerade(zahl)

Er ruft die statischen Methoden (da die Erweiterungsmethode in einem Module definiert wurde, ist diese implizit Shared) direkt auf und übergibt nacheinander die Parameter. Nichts besonderes, aber es erleichtert die Lesbarkeit des Codes, vor allem, wenn Methodenaufrufe geschachtelt werden.

Lambda Expressions (Lambda-Ausdrücke)
Lambda-Ausdrücke kann man sich als anonyme Methoden vorstellen, wie diese in C# 2.0 existieren. Damit können an den Stellen, wo ein Delegate erwartet wird ein Lambda-Ausdruck angegeben werden und man muss keine eigene Methode definieren.

Extensiven Gebrauch von Lambda-Ausdrücken wird bei LINQ gemacht. Dies sieht man als Entwickler nur bedingt, da man meistens mit den Abfrageoperatoren arbeitet. Diese werden aber vom Compiler in Erweiterungsmethoden und Lambda-Ausdrücke aufgelöst.

Bessere Integration von Nullable Types
Nullable Types gibt es seit VB2005 und .NET 2.0, in VB2008 wurde deren Integration in die Sprache noch verbessert. Während man in VB2005

Dim nz As Nullable(Of Integer) = 5

schrieb, kann man dies unter VB2008 etwas prägnanter schreiben:

Dim nz As Integer? = 5

Tertiäres If
Ein If-Then-Else kann verkürzt geschrieben werden zu einem tertiären If und VB2008 bietet nun ein Äquivalent zu der C#-Anweisung bedingung ? trueExpr : falseExpr. Ein Beispiel:

Dim zahl = 5
Dim res = If(zahl > 0, 1, -1)

In diesem Beispiel wird der Variable res der Wert 1 zugewiesen.

Die bisherigen Features benötigen nur den VB2008-Compiler, sind aber nicht an das .NET Framework 3.5 gebunden, d. h. sie funktionieren auch unter .NET 2.0 und 3.0.

Eine Einführung in LINQ
Die folgenden Abschnitte sollen einen Überblick über LINQ geben.

Was ist LINQ?
LINQ steht für Language Integrated Query. Damit können einheitlich Daten (Objekte, Datenbanken und XML) in einem deklarativen Stil abgefragt werden. Sie programmieren diese Abfragelogik nicht aus, sondern programmieren nur noch, was Sie wollen.

Bei LINQ arbeiten die Neuerungen in der Sprache VB2008 mit den neuen Bibliotheken des .NET Frameworks 3.5 zusammen. Nur diese zusammen ergeben LINQ.

Die LINQ-Provider
Im .NET Framework 3.5 findet man 3 LINQ-Provider:

  • LINQ to Objects, mit dem Objektmengen (Arrays, Collections, …) abgefragt werden können
  • LINQ to SQL, womit einfaches O/R-Mapping für MS SQL Server möglich ist
  • LINQ to XML, womit auf eine sehr einfache Art und Weise XML-Dokumente und -Fragmente erstellt und manipuliert werden können.

Mit dem SP1 von VS2008 und .NET 3.5 kommt ein weiterer Provider hinzu: LINQ to Entities als Bestandteil des ADO.NET Entity Frameworks. Dies ist ein O/R-Mapper wie LINQ to SQL, bietet allerdings mehr Mappingmöglichkeiten und es wird Unterstützung von Nicht-MS-Datenbanken geben.

Weiterhin lassen sich auch eigene LINQ-Provider schreiben. So existieren bereits zahlreiche Provider, u. a. LINQ to Amazon, LINQ to Flickr und LINQ to Google. Eine Liste mit LINQ Provider hat Charlie Calvert (Mitarbeiter bei Microsoft USA) auf seinem Blog veröffentlicht.

Erste Schritte mit LINQ to Objects
Im ersten LINQ-Beispiel sollen alle Prozesse ermittelt werden, deren Namen mit einem „e“ beginnen oder deren RAM-Bedarf größer als 10000000 Bytes ist. Bisher hätte eine Lösung ungefähr so ausgesehen:

Dim result As New List(Of String)
 
For Each p In Process.GetProcesses()
  If p.ProcessName.StartsWith("e") Or _
  p.WorkingSet64 > 10000000 Then
    result.Add(p.ProcessName)
  End If
Next

Dank LINQ kann dies etwas kürzer geschrieben werden:

Dim result = _
From p In Process.GetProcesses() _
Where p.ProcessName.StartsWith("e") Or p.WorkingSet64 > 10000000 _
Select p.ProcessName

Für den VB-Compiler ist dies eine Anweisung, welche ich aber aufgrund der Überschaubarkeit auf mehrere Zeilen umgebrochen habe.

Eine LINQ-Abfrage beginnt immer mit einem oder mehreren From-Klauseln, welche die Datenquellen definieren. Danach folgen zahlreiche Datenmanipulationen (Filterung mit Where, Gruppierung mit Group By, Sortierung mit Order By, Datenverknüpfung mit Join, …) und zum Schluss die Selektierung der Daten über die Select-Klausel (in einigen Fällen kann das Select entfallen, z. B. nach einem Skip- oder Take-Operator).

Der VB-Compiler löst obige LINQ-Abfrage wie folgt in Erweiterungsmethoden und Lambda-Ausdrücke auf:

Dim result = Process.GetProcesses().Where( _
Function(p) p.ProcessName.StartsWith("e") _
Or p.WorkingSet64 > 10000000).Select(Function(p) p.ProcessName)

Wie Sie sehen, wird dies gerade von Type Inference sehr massiv verwendet. Die Parameter der Lambda-Ausdrücke, welche mit dem Schlüsselwort Function beginnen, können einfach nur benannt werden und der Typ wird entweder vom Compiler abgeleitet oder explizit notiert.

Ein weiteres Beispiel: Es sollen in einem Ordner alle Dateien gelöscht werden, außer den drei neuesten. Mit Hilfe von LINQ lassen sich die alten Dateien sehr schnell ermitteln:

Dim dir As New DirectoryInfo("E:\WebSites\Test")
 
Dim oldFiles = _
From fi In dir.GetFiles() _
Order By fi.CreationTime Descending _
Skip 3
 
For Each fi In oldFiles
  fi.Delete()
Next 

Durch die absteigende Sortierung nach dem Erstellungsdatum befinden sich die drei neuesten Dateien am Anfang der Ergebnismenge, welche übersprungen werden. Das Resultat sind die alten Dateien, die alle nacheinander gelöscht werden können.

LINQ to SQL
Für LINQ to SQL muss als erstes das Mapping zwischen den Datenbanktabellen und den Klassen definieren. Sehr schnell geht dies über die Elementvorlage „LINQ to SQL Klassen“. Diese öffnet einen Designer, in den man einfach die Datenbanktabellen ziehen kann. Für die einzelnen Tabellen werden Klassen erzeugt sowie auch die Abhängigkeiten zwischen den Tabellen berücksichtigt. Außerdem wird eine typisierte DataContext-Klasse generiert, die Zugriff auf die Tabellen erlaubt und sich um den Zugriff auf die Datenbank kümmert.

Was möglich ist, soll folgendes komplexeres Beispiel zeigen. Aus der Beispieldatenbank Northwind sollen alle Produkte einer Kategorie ermittelt werden und nach der jeweiligen Kategorie gruppiert werden. Weiterhin sollen alle Produkte innerhalb der Kategorien summiert werden. Diese Aufgabe erledigt folgende LINQ-Abfrage inkl. Ausgaben:

Using ctx As New NorthwindDataContext()
  ctx.Log = Console.Out
  Dim res = _
  From c In ctx.Categories _
  Group Join p In ctx.Products On c.CategoryID Equals _
    p.CategoryID Into Products = Group _
  Aggregate p In Products Into Sum(p.UnitPrice * p.UnitsInStock) _
  Select New With {Key .Category = c.CategoryName, _
    .CategorySum = Sum, .Products = Products}
 
  For Each item In res
    Console.WriteLine("{0} (Wert: {1})", item.Category, _
      Math.Round(item.CategorySum.Value, 2).ToString())
    For Each p In item.Products
      Console.WriteLine(vbTab & p.ProductName)
    Next
    Console.WriteLine()
  Next
End Using

Der DataContext bietet ein paar Einstellungsmöglichkeiten (z. B. Einflussmöglichkeiten auf das Laden von Objekten) und auch eine Loggingfunktionalität. Das generierte Log wird hier auf die Konsole umgeleitet, welche auch das generierte SQL enthält:

SELECT [t3].[CategoryName] AS [Category], [t3].[value] AS 
[CategorySum], [t4].[ProductID], [t4].[ProductName], 
[t4].[SupplierID], [t4].[CategoryID], [t4].[QuantityPerUnit], 
[t4].[UnitPrice], [t4].[UnitsInStock], [t4].[UnitsOnOrder], 
[t4].[ReorderLevel], [t4].[Discontinued], (
  SELECT COUNT(*)
  FROM [dbo].[Products] AS [t5]
  WHERE ([t3].[CategoryID]) = [t5].[CategoryID]
  ) AS [value]
FROM (
  SELECT [t0].[CategoryID], [t0].[CategoryName], (
    SELECT SUM([t2].[value])
    FROM (
      SELECT [t1].[UnitPrice] * 
(CONVERT(Decimal(29,4),CONVERT(Int,[t1].[UnitsInStock]))) 
AS [value], [t1].[CategoryID] 
      FROM [dbo].[Products] AS [t1] 
      ) AS [t2]
    WHERE ([t0].[CategoryID]) = [t2].[CategoryID]
SELECT [t3].[CategoryName] AS [Category], [t3].[value] AS 
[CategorySum], [t4].[ProductID], [t4].[ProductName], 
[t4].[SupplierID], [t4].[CategoryID], [t4].[QuantityPerUnit], 
[t4].[UnitPrice], [t4].[UnitsInStock], [t4].[UnitsOnOrder], 
[t4].[ReorderLevel], [t4].[Discontinued], (
  SELECT COUNT(*)
  FROM [dbo].[Products] AS [t5]
  WHERE ([t3].[CategoryID]) = [t5].[CategoryID]
  ) AS [value]
FROM (
  SELECT [t0].[CategoryID], [t0].[CategoryName], (
    SELECT SUM([t2].[value])
    FROM (
      SELECT [t1].[UnitPrice] * 
(CONVERT(Decimal(29,4),CONVERT(Int,[t1].[UnitsInStock]))) AS 
[value], [t1].[CategoryID]
      FROM [dbo].[Products] AS [t1]
      ) AS [t2]
    WHERE ([t0].[CategoryID]) = [t2].[CategoryID]
    ) AS [value]
  FROM [dbo].[Categories] AS [t0]
  ) AS [t3]
LEFT OUTER JOIN [dbo].[Products] AS [t4] ON ([t3].[CategoryID]) = 
[t4].[CategoryID]
ORDER BY [t3].[CategoryID], [t4].[ProductID]
    ) AS [value]
  FROM [dbo].[Categories] AS [t0]
  ) AS [t3]
LEFT OUTER JOIN [dbo].[Products] AS [t4] ON ([t3].[CategoryID]) = 
[t4].[CategoryID]
ORDER BY [t3].[CategoryID], [t4].[ProductID]

Das generierte SQL ist in diesem Beispiel schon komplexer Natur und die LINQ-Abfrage ist nach etwas Einarbeitungszeit innerhalb von maximal einer Minute getippt. Für dieses SQL wird man schon mehr Zeit benötigen.

LINQ to XML
Das .NET Framework besitzt seit der Version 1.0 XML-Funktionalität. Mit der jetzigen Version 3.5 kommt eine weitere XML-API dazu: LINQ to XML. Diese ist leicht verständlich und man kann mit ganzen XML-Dokumenten wie auch einzelnen Fragmenten arbeiten.

Das Ergebnis der LINQ to SQL-Abfrage soll in einem XML-Report transformiert werden. Dies geht dank den XML-Literalen von VB2008 (hat C# 3.0 nicht) und ein eingebetteten Abfragen (erkennbar an <%= => Syntax) sehr gut von der Hand:

Dim xml = _
<report>
  <%= From c In res Select _
    <category name=<%= c.Category %> sum=<%= c.CategorySum %>>
      <%= From p In c.Products Select _
        <product><%= p.ProductName %></product> %>
    </category> _
  %>
</report>

Man beachte, dass man direkt XML in VB-Code schreibt! Dies ist ein echt geniales Feature!

Zusammenfassung und Ausblick
Mit dem LINQ-Framework ist es bedeutend einfacher, Daten abzufragen und zu manipulieren. Durch den deklarativen Stil spart man Zeit, da man sich nicht mit Implementierungsdetails herumplagen muss.

LINQ bringt zwar einen gewissen Overhead mit sich, aber die gewonnene Produktivität ist um ein vielfaches höher. Mit dem kommenden ParallelFX-Framework von Microsoft, welches ebenfalls einen LINQ-Provider (PLINQ) enthält, können LINQ-Abfragen parallelisiert werden und so mit minimalem Aufwand das Potenzial moderner Mehrkernprozessoren ausschöpfen.

Zu LINQ wird es noch eine Workshopserie geben, in der die einzelnen LINQ-Provider tiefgründiger beleuchtet werden.



Anzeige

Kauftipp Unser Dauerbrenner!Diesen und auch alle anderen Workshops finden Sie auch auf unserer aktuellen vb@rchiv  Vol.6

Ein absolutes Muss - Geballtes Wissen aus mehr als 8 Jahren vb@rchiv!
- nahezu alle Tipps & Tricks und Workshops mit Beispielprojekten
- Symbol-Galerie mit mehr als 3.200 Icons im modernen Look
Weitere Infos - 4 Entwickler-Vollversionen (u.a. sevFTP für .NET), Online-Update-Funktion u.v.m.
 
 
Copyright ©2000-2024 vb@rchiv Dieter OtterAlle Rechte vorbehalten.


Microsoft, Windows und Visual Basic sind entweder eingetragene Marken oder Marken der Microsoft Corporation in den USA und/oder anderen Ländern. Weitere auf dieser Homepage aufgeführten Produkt- und Firmennamen können geschützte Marken ihrer jeweiligen Inhaber sein.