Local Type Inference (lokaler Typrückschluss) 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:
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) 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) 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) 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) 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 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 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 Was ist LINQ? 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
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 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 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 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 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. Dieser Workshop wurde bereits 16.179 mal aufgerufen.
Anzeige
![]() ![]() ![]() 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. |
vb@rchiv CD Vol.6 ![]() ![]() Geballtes Wissen aus mehr als 8 Jahren vb@rchiv! Online-Update-Funktion Entwickler-Vollversionen u.v.m. Tipp des Monats ![]() Manfred Bohn IndexOf für mehrdimensionale Arrays Die generische Funktion "IndexOf" ermittelt das erste Auftreten eines bestimmten Wertes in einem n-dimensionalen Array sevZIP40 Pro DLL ![]() Zippen und Unzippen wie die Profis! Mit nur wenigen Zeilen Code statten Sie Ihre Anwendungen ab sofort mit schnellen Zip- und Unzip-Funktionen aus. Hierbei lassen sich entweder einzelnen Dateien oder auch gesamte Ordner zippen bzw. entpacken. |
|||||||||||||
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. |