Mit der "Language Integrated Query" steht in VB2008 ein Konzept zur Verfügung, durch das der Zugriff auf Daten in verschiedenen Quellen durch einheitlichen VB-Code möglich ist. Das funktioniert nicht nur bei externen Datenquellen, sondern kann auch bei im Hauptspeicher abgelegten Daten z.B. System.Arrays angewendet werden ('LINQ To Objects', d.h. ohne Verwendung eines LINQ-Anbieters). Dieser Workshop kann nur einen kleinen Überblick über die Fülle der Möglichkeiten von 'LINQ To Object' bieten. Die Beispiele eignen sich zur ersten praktischen 'Kontaktaufnahme', sobald man die einführenden Abschnitte in der VB-Dokumentation gelesen hat. Der Zugriff auf die von LINQ im Speicher erstellte Datensequenz erfolgt durch den Enumerator der generischen Schnittstelle IEnumerable(Of T), die im Namespace 'System.Collections.Generic' enthalten ist. Um Linq-Abfragen bei System.Arrays über diese Schnittstelle zu verwenden, müssen sie 'queryable' sein, d.h. es werden die Erweiterungen benötigt, die im Namespace 'System.Linq.Enumerable' enthalten sind. VB-Doku: Bei Methoden, die für Auflistungen im Arbeitsspeicher ausgeführt werden, also Methoden, die IEnumerable(Of (T)) erweitern, erfasst das zurückgegebene aufzählbare Objekt die an die Methode übergebenen Argumente. Wenn dieses Objekt aufgezählt wird, wird die Logik des Abfrageoperators angewendet, und die Abfrageergebnisse werden zurückgegeben. Solche LINQ-Abfragen sind auch möglich bei mehrdimensionalen Arrays und bei Arrays, deren Elemente Instanzen eines Objekts sind. Einige grundsätzliche Anmerkungen:
Übersicht über die Beispiele zur Gestaltung von LINQ-Abfragen bei Daten in System.Arrays:
Jedes Beispiel steht als Routine in einem Modul, damit auch die jeweils nötigen bzw. möglichen OPTION- und IMPORT-Anweisungen erkennbar werden. Um ein Beispiel auszuführen, erstellt man eine Windows-Anwendung und fügt ein Modul ein. Der in das Modul kopierte Code wird in der Form_Load-Routine des Startformulars als Routine aufgerufen z.B. durch 'Ex1'. Es ist nur der NameSpace 'System' zu importieren. Alle weiteren Namespaces werden in den entsprechenden Modulen durch Imports-Statements oder explizit im Code einbezogen. Das soll der schnellen Orientierung im VB-'Objektraum' dienen. Beispiel 1: Eindimensionales Double-Array Bei der LINQ-Abfrage eines eindimensionalen Arrays kann die Bereichsvariable ('range_var') und die Sequenzvariable ('seq') explizit vereinbart werden. Die Anweisung 'OPTION INFER OFF' ist deshalb möglich. Hat man mehrere LINQ-Abfragen in einer Function/Sub, dürfen sich die Bezeichnungen der Sequenzvariablen nicht wiederholen. Die Bereichsvariablen können nicht als lokale Variable der Function/Sub deklariert werden, sondern innerhalb der Abfrage. Im Beispiel werden Array-Elemente gefiltert und fallend sortiert.Es entsteht eine 'in-memory-query' deren Elemente z.B. durch eine FOR-NEXT-Schleife abgefragt werden oder per Index durch die Default-Erweiterung 'ElementAt'. Die Erweiterung 'Count' informiert darüber, ob und wie viele Elemente die Query umfasst. Die Erweiterung 'ToArray' übertrõgt den Inhalt der Query in ein System-Array. Die Aggregate-Klausel erlaubt die Berechnung von Kennwerten zu einer Query-Variable (im Beispiel: Berechnung der Summe der gefilterten Elemente durch die Standard-Aggregatfunktion 'Sum'). Bei der 'OrderBy'-Klausel ist allgemein zu beachten (VB-Doku): Option Strict On Option Explicit On Option Infer Off Imports System.Collections.Generic Imports System.Linq.Enumerable Module modLinqArray_Exempl01 ''' <summary>Beispiele für Double-Array</summary> Public Sub Ex1() Dim dbl_arr(99) As Double, i As Integer For i = 0 To 99 : dbl_arr(i) = Rand() : Next i Const mn As Double = 0.25, mx As Double = 0.75 Dim seq As IEnumerable(Of Double) = _ From range_var As Double In dbl_arr _ Where range_var > mn And range_var < mx _ Order By range_var Descending If seq.Count < 1 Then Exit Sub Dim prev_elem As Double = 100 Dim sum1 As Double = 0 For Each elem As Double In seq sum1 += elem ' decend-Sortierung testen If elem > prev_elem Then Stop prev_elem = elem Next elem Dim sum2 As Double = 0 For i = 0 To seq.Count - 1 sum2 += seq(i) ' entspricht: sum2 += q1.ElementAt(i) Next i If Math.Abs(sum2 - sum1) > 0.0001 Then Stop ' Übertragung in ein Array Dim db_arr() As Double = seq.ToArray Dim sum3 As Double = 0 For i = 0 To db_arr.Length - 1 sum3 += db_arr(i) Next i If Math.Abs(sum3 - sum1) > 0.0001 Then Stop ' Verwendung der Standardaggregatfunktion Dim sum4 As Double = _ Aggregate range_var As Double In dbl_arr _ Where (range_var > mn And range_var < mx) _ Into Sum() If Math.Abs(sum4 - sum1) > 0.0001 Then Stop Dim sum5 As Decimal = _ Aggregate range_var As Double In dbl_arr _ Where (range_var > mn And range_var < mx) _ Let dec As Decimal = CDec(range_var) _ Select dec Into Sum() If Math.Abs(sum5 - sum1) > 0.0001 Then Stop End Sub End Module Beispiel 2: Berechnung von Variablen Es ist auch bei Arrays möglich, in der LINQ-Abfrage neue Variablen zu berechnen und sie in die 'Query' einzutragen (LET-Klausel). Sobald in der abgefragten Datensequenz mehr als eine Variable steht, handelt es sich bei der Sequenzvariable um einen 'anonymen Datentyp', der per lokalem Typ-Rückschluss zur Kompilierungszeit 'strikt' vereinbart wird. Das folgende Beispiel erfordert deshalb OPTION INFER ON. Hinter der LET-Klausel kann die neu erstellte Variable ('new_var') in den folgenden Klauseln der Abfrage verwendet werden (im Beispiel: 'Order By'). Diese Variable kann einen anderen Datentyp besitzen als das Array - ggf. sind explizite Konvertierungen notwendig (z.B. 'CSng'). Die Erweiterungsmethode 'ToArray' erlaubt in diesem Fall die Übertragung des Inhalts der Datensequenz in ein Array des Typs 'Object'. Die Array-Elemente sind auch vom 'anonymen Typ', der die schreibgeschützten Eigenschaften 'range_var' und 'new_var' umfasst. Das gilt auch für die Schleifenkontrollvariable 'Elem'. Im Beispiel verwendet die Sequenzvariable 'seq_sort' die Schnittstelle 'IOrderedEnumerable' (von 'IEnumerable' vererbt), weil das 'Order By'-Statement in der Abfrage benutzt wird. Diese Schnittstelle ist im Namespace 'System.Linq' vereinbart. Ihre explizite Deklaration ist nicht bei allen Abfragen sortierter Sequenzen möglich. Diese Schnittstelle erlaubt eine effizientere Ausführung nachher aufgerufener untergeordneter Such-Extensions. Option Strict On Option Explicit On Option Infer On Imports System.Linq.Enumerable Module modLinqArray_Exempl02 ''' <summary>Berechnung einer Variable ''' (anonyme Sequenzvariable)</summary> Public Sub Ex2() Dim dbl_arr(99) As Double, i As Integer For i = 0 To 99 : dbl_arr(i) = Rand() : Next i Const mn As Double = 0.25, mx As Double = 0.75 Dim seq_anonym = _ From range_var As Double In dbl_arr _ Let new_var As Single = CSng(range_var * 10 * Rand()) _ Where range_var >= mn And range_var <= mx _ Order By new_var Descending ' explizit deklariertes Array Dim expl_arr() As Object = seq_anonym.ToArray ' Zugriff auf Array-Elemente erfordert ' OPTION STRICT OFF (--> späte Bindung!) ' Dim vl As Double = expl_arr(0).range_var ' Dim v2 As Double = expl_arr(0).new_var ' inferentiell deklariertes Array (strikt) Dim impl_arr() = seq_anonym.ToArray ' ---> frühe Bindung Dim v1 As Double = impl_arr(0).range_var Dim v2 As Double = impl_arr(0).new_var Dim prev_elem As Single = 1000 Dim sum As Double = 0 For Each elem In seq_anonym ' anonymer Typ --> frühe Bindung sum += elem.range_var ' Sortierung testen If elem.new_var > prev_elem Then Stop prev_elem = elem.new_var Next elem ' Bei der Abfrage sortierter Daten ist in ' vielen Fällen eine explizite Deklaration der ' IOrderedEnumerable'-Schnittstelle möglich Dim seq_sort As Linq.IOrderedEnumerable(Of Double) = _ From varia In dbl_arr _ Where varia > 0.3 _ Order By varia Descending End Sub End Module Beispiel 3: Auswahl von Variablen Durch die SELECT-Klausel kann man auszuwählen, welche Variable(n) in die 'Query' eingetragen werden. Falls nur eine Variable übernommen wird, kann die Sequenzvariable explizit (gemäß dem Typ der berechneten Variable) deklariert werden (im Beispiel als 'Single'). Das Statement 'OPTION INFER OFF' ist zulässig. Wenn man nur die Bereichsvariable in der Datensequenz benötigt und keine weitere Variable berechnet worden ist, kann die Select-KLausel weggelassen werden (vgl. Beispiel 1). Option Strict On Option Explicit On Option Infer Off Imports System.Collections.Generic Imports System.Linq.Enumerable Module modLinqArray_Exempl03 ''' <summary>Berechnung einer Variable</summary> Public Sub Ex3() Dim dbl_arr(99) As Double, i As Integer For i = 0 To 99 : dbl_arr(i) = Rand() : Next i Const mn As Double = 0.25, mx As Double = 0.75 ' Berechnung einer Variable des Typs 'Single' Dim seq As IEnumerable(Of Single) = _ From range_var As Double In dbl_arr _ Where range_var >= mn And range_var <= mx _ Let new_var As Single = CSng(range_var * 10 * Rand()) _ Order By new_var Descending _ Select new_var Dim prev_elem As Single = 100 Dim sum As Single = 0 For Each elem As Single In seq sum += elem ' Sortierung testen If elem > prev_elem Then Stop prev_elem = elem Next elem End Sub End Module Beispiel 4: Mehrdimensionales Array Beim Zugriff auf mehrdimensionale Arrays ist zu beachten, dass bei der Ausführung der Abfrage alle Array-Elemente durchlaufen werden - die entstehende Datensequenz ist aber 'eindimensional'. Eine explizite Deklaration der 'Query' und der Bereichsvariable kann als 'Object' erfolgen. In diesem Fall sind im Abfrage-Code geeignete Konvertierungsfunktionen erforderlich. Eine typspezifische Deklaration von Sequenz- und Bereichsvariable erfordert die Verwendung der Erweiterung 'Cast' der 'IEnumerable'-Schnittstelle. Im folgenden Beispiel führt die 'DISTINCT'-Klausel dazu, dass in der Sequenz keine Werte doppelt auftreten. Option Strict On Option Explicit On Option Infer Off Imports System.Collections.Generic Imports System.Linq.Enumerable Module modLinqArray_Exempl04 ''' <summary>mehrdimensionales Array (Werttyp)</summary> Public Sub Ex4() Dim dbl_arr3(3, 13, 23) As Double For i As Integer = 0 To 3 For k As Integer = 0 To 13 For l As Integer = 0 To 23 dbl_arr3(i, k, l) = Math.Round(Rand(), 1) Next l, k, i Const mini As Double = 0.25, maxi As Double = 0.55 ' Mehrdimensionales Array wird als 'Object' verarbeitet Dim obj_seq As IEnumerable(Of Object) = _ From range_var As Object In dbl_arr3 _ Where CDbl(range_var) > mini And CDbl(range_var) < maxi _ Order By range_var Ascending Distinct ' Mehrdimensionales Array wird 'gecastet' Dim dbl_seq As IEnumerable(Of Double) = _ From range_var As Double In dbl_arr3.Cast(Of Double)() _ Where range_var > mini And range_var < maxi _ Order By range_var Ascending Distinct Dim sum1, sum2, sum3 As Double For Each elem As Double In obj_seq ' Filter testen If elem < mini Then Stop If elem > maxi Then Stop ' Summe durch Iteration sum1 += elem Next elem ' Berechnung der Summe durch Aggregatfunktion sum2 = Aggregate range_var As Object In dbl_arr3 _ Where CDbl(range_var) > mini And CDbl(range_var) < maxi _ Distinct Into Sum(CDbl(range_var)) If Math.Abs(sum1 - sum2) > 0.0001 Then Stop sum3 = Aggregate range_var As Double _ In dbl_arr3.Cast(Of Double)() _ Where range_var > mini And range_var < maxi _ Distinct Into Sum() If Math.Abs(sum1 - sum3) > 0.0001 Then Stop End Sub End Module Beispiel 5: Arrays, deren Elemente Instanzen eines Objekts sind Zur Demonstration der LINQ-Abfragen bei Arrays, deren Elemente Objekte sind, wird eine Klasse verwendet, die einen Double-Wert kapselt ('cData') und im Konstruktor die Möglichkeit bietet, die Arrayindices der Instanz zu speichern. Die Implementierung der ICloneable-Schnittstelle erlaubt die Erstellung von Kopien einer Instanz. ''' <summary>Demo-Klasse für LINQ 2 Array</summary> Public Class cData Implements ICloneable ' Membervariable der Klasse Dim _dbl As Double, _index1, _index2, _index3 As Integer ' Konstruktor Public Sub New(ByVal dbl As Double, _ ByVal index1 As Integer, _ Optional ByVal index2 As Integer = -1, _ Optional ByVal index3 As Integer = -1) _dbl = dbl _index1 = index1 : _index2 = index2 : _index3 = index3 End Sub ' Abfragen/Setzen des Wertes Public Property DBL() As Double Get Return _dbl End Get Set(ByVal value As Double) _dbl = value End Set End Property Public ReadOnly Property Index1() As Integer Get Return _index1 End Get End Property Public ReadOnly Property Index2() As Integer Get Return _index2 End Get End Property Public ReadOnly Property Index3() As Integer Get Return _index3 End Get End Property ' Kopie der Objektinstanz Public Function Clone() As Object _ Implements System.ICloneable.Clone Return Me.MemberwiseClone() End Function End Class Bei einem eindimensionalen Array, dessen Elemente Objektinstanzen (der gleichen Klasse) sind, kann die Sequenzvariable explizit deklariert werden. Die Methoden und Eigenschaften des Objekts stehen in der Sequenzvariable zur Verfügung. Die 'in-memory-query' kann durch die Erweiterung 'ToArray' in ein Array eingetragen werden. Allerdings ist dabei zu beachten, dass die Array-Elemente Referenzen auf die Objekt-Instanzen des Quell-Arrays der LINQ-Abfrage sind. Im Beispiel würden sich Änderungen in dem Array 'ref_arr' auch auf die entsprechenden Elemente in dem Array 'obj_arr' auswirken. Die Elemente in den Arrays 'arq_cl1' und 'arq_cl2' sind neu erstellte Objektinstanzen und verweisen nicht auf 'obj_arr'. Das Resultat der Erweiterung 'Count' wird in einer Variable gespeichert, weil auf diesen Wert mehrfach zugegriffen wird. Option Strict On Option Explicit On Option Infer Off Imports System.Collections.Generic Imports System.Linq.Enumerable Module modLinqArray_Exempl05 ''' <summary>Array mit Elementen vom Typ Object</summary> Public Sub Ex5() Dim obj_arr(99) As cData For i As Integer = 0 To 99 obj_arr(i) = New cData(Rand, i) Next i Dim obj_seq As IEnumerable(Of cData) = _ From dt As cData In obj_arr _ Where dt.DBL < 0.25 Or dt.DBL > 0.75 _ Order By dt.DBL Ascending Dim sum1 As Double = 0 For Each elem As cData In obj_seq sum1 += elem.DBL ' Filter testen If elem.DBL >= 0.25 And elem.DBL <= 0.75 Then Stop Next elem ' Zahl der Elemente in der Sequenz speichern Dim N As Integer = obj_seq.Count ' Referenz auf Array-Elemente übertragen Dim ref_arr() As cData = obj_seq.ToArray ' Array-Elemente anhand der Query neu erstellen ' Nutzung des Konstruktors der Klasse 'cData' Dim arq_cl1(N - 1) As cData For i As Integer = 0 To N - 1 With obj_seq(i) arq_cl1(i) = New cData(.DBL, .Index1, .Index2, .Index3) End With Next i ' Array-Elemente anhand der Query neu erstellen ' Nutzung des Clone-Implementierung der Klasse 'cData' Dim arq_cl2(N - 1) As cData For i As Integer = 0 To N - 1 arq_cl2(i) = CType(obj_seq(i).Clone, cData) Next i ' Summe der Arrayelemente berechnen Dim sum2 As Double = 0 For i As Integer = 0 To ref_arr.Length - 1 sum2 += ref_arr(i).DBL Next i If Math.Abs(sum1 - sum2) > 0.000001 Then Stop ' Summe per Aggregatfunktion berechnen Dim sum3 As Double = _ Aggregate dt As cData In obj_arr _ Where dt.DBL < 0.25 Or dt.DBL > 0.75 _ Into Sum(dt.DBL) If Math.Abs(sum1 - sum3) > 0.000001 Then Stop End Sub End Module Beispiel 6: Mehrdimensionales Array, dessen Elemente Instanzen eines Objekts sind Bei einem mehrdimensionalen Array kann die Sequenz- und die Bereichsvariable explizit vereinbart werden, wenn die 'Cast-Erweiterung verwendet wird. Im Beispiel wird auf ein dreidimensionales Array zugegriffen. Die Array-Indices werden in den Objekt-Instanzen gespeichert. Auf diese Weise ist es möglich, bei den Verweisen in der Datensequenz die Array-Indices der Instanz abzufragen. Die Elemente der 'Query' werden hierarchisch anhand der drei Index-Eigenschaften der Objekte sortiert. Aus den Elementen in der Datensequenz werden geklonte Objektinstanzen als Elemente eines eindimensionalen Array ('cln_arq') erstellt. Option Strict Off Option Explicit On Option Infer Off Imports System.Collections.Generic Imports System.Linq.Enumerable Module modLinqArray_Exempl06 ''' <summary> ''' Mehrdimensionales Array (Element: Objektreferenz)</summary> Public Sub Ex6() Dim obj_arr3(4, 5, 6) As cData For i As Integer = 0 To 4 For k As Integer = 0 To 5 For l As Integer = 0 To 6 obj_arr3(i, k, l) = New cData(Rand, i, k, l) Next l, k, i Dim data_seq As IEnumerable(Of cData) = _ From range_var As cData In obj_arr3.Cast(Of cData)() _ Where CDbl(range_var.DBL) > 0.4 _ Order By range_var.Index3 Descending, _ range_var.Index2 Descending, _ range_var.Index1 Descending Dim sum1 As Double = 0 For Each elem As cData In data_seq sum1 += elem.DBL ' Filter prüfen If elem.DBL <= 0.4 Then Stop With elem ' Korrespondenz der Query mit dem Array prüfen If .DBL <> obj_arr3(.Index1, .Index2, .Index3).DBL Then Stop End With Next elem Dim N As Integer = data_seq.Count ' Array-Elemente anhand der Query neu erstellen Dim cln_arr(N - 1) As cData For i As Integer = 0 To N - 1 cln_arr(i) = data_seq(i).Clone Next i Dim sum2 As Double = 0 For i As Integer = 0 To cln_arr.Length - 1 sum2 += cln_arr(i).DBL Next i If Math.Abs(sum1 - sum2) > 0.000001 Then Stop End Sub End Module Beispiel 7: Verknüpfung von Arrays Durch LINQ-Abfragen können auch Arrays miteinander verknüpft werden. Eine Liste von Arrays im From-Abschnitt der Abfrage bewirkt die Ausführung einer geschachtelten Schleife. Der JOIN-Operator bewirkt, dass jedes Element im Array vor 'JOIN' mit jedem Element im Array nach 'JOIN' kombiniert wird. Für 'Mengenvorgõnge' stehen Erweiterungen von 'IEnumerable' zur Verfügung (Except, Intersect, Union -- Rest-, Schnitt-, Vereinigungsmenge). Man beachte, dass diese Erweiterungen jedes unterscheidbare Element NUR EINMAL in die Sequenz übernehmen. 'Distinct' ist als VB-Schlüsselwort verfügbar. Das folgende Beispiel liefert diejenigen Werte, die in beiden Arrays vorkommen (ohne Beachtung, ob auch die Array-Indices identisch sind!). Die Daten werden von der Erweiterung 'ToArray' SOFORT in ein Double-Array eingetragen, ohne dass die Deklaration einer Sequenzvariable und die Speicherung der Abfrage erforderlich ist. Es wird ein Double- mit einem Single-Array kombiniert. Das Equals-Schlüsselwort funktioniert in diesem Fall nur korrekt, wenn beide Range-Variable vom Typ 'Single' sind. (Es handelt sich bei dieser Anwendung genau genommen um eine Zweckentfremdung, weil gewöhnlich nicht Daten-Werte, sondern 'Schlüssel' verglichen werden.) Für die Intersect-Erweiterung gilt das gleiche. Möchte man die Elemente aus zwei Auflistungen indexbasiert kombinieren, kann man die Erweiterung 'JoinIndex' im Modul 'modLinqArray_Help' (Beispiel 9) verwenden. Sie erstellt eine Auflistung des Typs 'Object' deren Elemente anonyme Typen sind, die die zusammengeführten Array-Elemente enthalten. Option Strict On Option Explicit On Option Infer Off Imports System.Collections.Generic Imports System.Linq.Enumerable Module modLinqArray_Exempl07 ''' <summary>Verknüpfung von Arrays</summary> Public Sub Ex7() Dim dbl_arr(9) As Double, i As Integer For i = 0 To dbl_arr.Length - 1 dbl_arr(i) = Math.Round(Rand(), 1) Next i Dim sng_arr(19) As Single For i = 0 To sng_arr.Length - 1 sng_arr(i) = CSng(Math.Round(Rand(), 1)) Next i ' Die gespeicherte Abfrage kombiniert jedes Element ' in dbl_arr mit allen Elementen in sng_arr ' Die Sequenz enthielte deshalb 10*20 = 200 Elemente Dim dec_seq As IEnumerable(Of Decimal) = _ From x As Double In dbl_arr, y As Single In sng_arr _ Let z As Decimal = CDec(x + y) _ Select z ' Das Zielarray ('db_arr') enthält eine sortierte Liste der ' unterscheidbaren Werte, die in beiden Arrays vorkommen ' ToArray --> unmittelbare Ausführung der Abfrage ' Double muss in Single konvertiert werden!! Dim db_arr() As Double = _ (From x As Double In dbl_arr _ Distinct _ Join y As Single In sng_arr _ On CSng(x) Equals y _ Distinct Order By x Select x).ToArray ' so geht es auch ... ' (Die zweite Query wird als Argument der Intersect-Erweiterung ' der ersten Query eingesetzt.) ' x muss hier in 'Single' konvertiert werden Dim d_arr() As Single = _ ((From x As Double In dbl_arr _ Let sng As Single = CSng(x) _ Order By sng Select sng). _ Intersect(From y As Single In sng_arr)).ToArray ' Kombination der Arrays (10 Elemente!) Dim obj_seq As IEnumerable(Of Object) = _ dbl_arr.JoinIndex(sng_arr) End Sub End Module Beispiel 8: Erweiterungsmethoden der 'IEnumerable'-Schnittstelle Die 'IEnumerable'-Schnittstelle bietet eine Fülle von 'Extensions', die eine direkte Weiterverarbeitung (z.B. Verknüpfen, Sortieren, Gruppieren, Filtern) der enthaltenen Daten erlauben. Für viele dieser Operationen steht kein entsprechendes VB-Schlüsselwort zur Verfügung (vgl. Beispiel 7). Näheres dazu im Kapitel 'Übersicht über Standardabfrageoperatoren' in der VB-Dokumentation. Die Erweiterung 'ToQuery' (im Modul 'modLinqArray_Help' - siehe Beispiel 9) überträgt die Elemente eines Array in eine 'Query'. Option Strict On Option Explicit On Option Infer Off Imports System.Collections.Generic Imports System.Linq.Enumerable Module modLinqArray_Exempl08 ''' <summary> ''' Erweiterungen der IEnumerable-Schnittstelle</summary> Public Sub Ex8() Dim int_arr1(9) As Integer, int_arr2(9) As Integer For i As Integer = 0 To 9 int_arr1(i) = CInt(Rand() * 10) int_arr2(i) = CInt(Rand() * 10) Next i ' Die Arrays in Sequenzen eintragen ' ---> per Linq-Abfrage Dim int_seq1 As IEnumerable(Of Integer) = _ From range_var As Integer In int_arr1 ' ---> per Erweiterung von System.Array ' vereinbart im Modul 'modLinqArray_Help' Dim int_seq2 As IEnumerable(Of Integer) = _ int_arr2.ToQuery ' 2. Sequenz an 1. Sequenz hinten anhängen Dim kombi_seq As IEnumerable(Of Integer) = _ int_seq1.Concat(int_seq2) ' nur unterscheidbare Elemente übernehmen Dim dist_seq As IEnumerable(Of Integer) = kombi_seq.Distinct ' Query sortieren Dim sort_seq As System.Linq.IOrderedEnumerable(Of Integer) = _ dist_seq.OrderBy(Function(x As Integer) x) ' alle Werte <= 4 in der sortierten Sequenz aussondern Dim seq As IEnumerable(Of Integer) = _ sort_seq.SkipWhile(Function(x As Integer) x <= 4) End Sub End Module Beispiel 9: Die kontrollierte Verarbeitung von IEEE-Werten (selbsterstellte Erweiterungen) Bei der Verwendung von LINQ-Klauseln bei Gleitkomma-Variablen sind die üblichen Vorsichtsma¯nahmen erforderlich (IEEE-Sonderwerte, keine Ausnahmen). Durch Verwendung geeigneter Erweiterungsmethoden lassen sich aber Probleme vermeiden bzw. abfangen. Im Beispiel wird die Erweiterung 'IsValid' zum Aussortieren von IEEE-Sonderwerten im abgefragten Array verwendet. Die Erweiterung 'Check' erlaubt das Auslösen einer Ausnahme beim Überlauf oder bei Double.NaN-Werten, wenn Variable neu berechnet werden. Die Erweiterungen sind in dem Modul 'modLinqArray_Help' vereinbart. Man beachte, dass Ausnahmen erst ausgelöst werden, sobald die Datensequenz in der FOR-NEXT-Schleife weiterverarbeitet wird (verzögerte Ausführung). Bei der Speicherung der LINQ-Abfrage in der Sequenzvariable geschieht zunächst nichts. Option Strict On Option Explicit On Option Infer Off Imports System.Collections.Generic Imports System.Linq.Enumerable Module modLinqArray_Exempl09 ''' <summary>IEEE-Sonderwerte behandeln</summary> Public Sub Ex9() ' Array mit einigen Sonderwerten füllen Dim dbl_arr(99) As Double For i As Integer = 0 To 99 If i Mod 5 = 0 Then dbl_arr(i) = Double.NaN ElseIf i Mod 7 = 0 Then dbl_arr(i) = Double.NegativeInfinity Else dbl_arr(i) = Math.Round(Rand() * 10) End If Next i ' Beispiel: Filtern invalider der Werte im Array Dim seq_valid As IEnumerable(Of Double) = _ From range_var In dbl_arr _ Where range_var.IsValid And range_var > 5 ' Beispiel: Überwachung der Berechnung von 'new_var' Dim seq_check As IEnumerable(Of Double) = _ From range_var In dbl_arr _ Where range_var.IsValid _ Let new_var As Double = (100.0 / range_var).check _ Select range_var ' Bei Ausführung der Schleife tritt die Ausnahme auf: ' wenn 'range_var' den Wert 0 enthält, wird 'elem' unendlich Dim sum As Double = 0 For Each elem As Double In seq_check sum += elem Next elem End Sub End Module Option Strict On Option Explicit On Option Infer Off Imports System.Collections.Generic Imports System.Linq.Enumerable ''' <summary> ''' Hilfsfunktionen f³r LINQ-Abfragen bei System.Arrays</summary> Module modLinqArray_Help ''' <summary> ''' Überprüfung von numerischen Werten ''' Bereich Double.MinValue - Double.MaxValue</summary> ''' <param name="value">zu prüfender Wert</param> ''' <returns>Wert im Geltungsbereich Double?</returns> <System.Runtime.CompilerServices.Extension()> _ Public Function IsValid(Of T As Structure) (ByVal value As T) As Boolean Try Dim dbl As Double = Convert.ToDouble(value) Return dbl >= Double.MinValue And _ dbl <= Double.MaxValue Catch Return False End Try End Function ''' <summary>Erweiterung löst eine Ausnahme aus, ''' falls Double-Wert ausserhalb von MinValue, MaxValue ''' </summary> ''' <param name="value">zu prüfender Wert</param> <System.Runtime.CompilerServices.Extension()> _ Public Function check(Of T As Structure) (ByVal value As T) As T Dim dbl As Double = Convert.ToDouble(value) If Double.IsNaN(dbl) Then Throw New InvalidCastException End If If Double.IsInfinity(dbl) Then Throw New OverflowException End If Return value End Function ''' <summary> ''' Übertragung der Elemente eines eindimensionalen Array ''' (Elemente = Werttyp) in eine 'Query'</summary> ''' <typeparam name="T">Typ der Array-Elemente</typeparam> ''' <param name="arr">einzutragendes Array</param> ''' <returns>Eine 'Query' vom Typ der Arrayelemente</returns> <Runtime.CompilerServices.Extension()> _ Public Function ToQuery(Of T As Structure)(ByVal arr As T()) As IEnumerable(Of T) Return From x In arr End Function ''' <summary> ''' Übertragung der Elemente eines zweidimensionalen Array ''' (Elemente = Werttyp) in eine 'Query'</summary> ''' <typeparam name="T">Typ der Array-Elemente</typeparam> ''' <param name="arr">einzutragendes Array</param> ''' <returns>Eine 'Query' vom Typ der Arrayelemente</returns> <Runtime.CompilerServices.Extension()> _ Public Function ToQuery(Of T As Structure)(ByVal arr As T(,)) As IEnumerable(Of T) Return (From elem As Object In arr).Cast(Of T)() End Function ''' <summary> ''' Übertragung der Elemente eines dreidimensionalen Array ''' (Elemente = Werttyp) in eine 'Query'</summary> ''' <typeparam name="T">Typ der Array-Elemente</typeparam> ''' <param name="arr">einzutragendes Array</param> ''' <returns>Eine 'Query' vom Typ der Arrayelemente</returns> <Runtime.CompilerServices.Extension()> _ Public Function ToQuery(Of T As Structure)(ByVal arr As T(,,)) As IEnumerable(Of T) Return (From elem As Object In arr).Cast(Of T)() End Function '''' <summary> '''' Übertragung der Elemente eines eindimensionalen '''' Array in eine 'Query'</summary> '''' <typeparam name="T">Typ der Array-Elemente</typeparam> '''' <param name="arr">einzutragendes Array</param> '''' <returns>Eine 'Query' vom Typ der Arrayelemente</returns> <Runtime.CompilerServices.Extension()> _ Public Function EnumToQuery(Of T) (ByVal arr As T()) As IEnumerable(Of T) Return (From elem As Object In arr).Cast(Of T)() End Function ''' <summary> ''' Indexbasierte Kombination zweier Auflistungen</summary> ''' <typeparam name="T1">Datentyp 1. Auflistung</typeparam> ''' <typeparam name="T2">Datentyp 2. Auflistung</typeparam> ''' <param name="data1">Erste Auflistung</param> ''' <param name="data2">Zweite Auflistung</param> ''' <returns>Kombinierte Auflistung</returns> <Runtime.CompilerServices.Extension()> _ Public Function JoinIndex(Of T1, T2) (ByVal data1 As IEnumerable(Of T1), _ ByVal data2 As IEnumerable(Of T2)) As IEnumerable(Of Object) Dim N As Integer = data1.Count Dim N2 As Integer = data2.Count N = Math.Min(N, N2) Dim ao(N - 1) As Object For i As Integer = 0 To N - 1 ao(i) = New With {.value1 = data1(i), .value2 = data2(i)} Next i Return From x In ao End Function ''' <summary>Zufallszahl im Bereich 0--1</summary> ''' <returns>Zufallszahl (per Rnd)</returns> Public Function Rand() As Double Return Microsoft.VisualBasic.Rnd End Function End Module Beispiel 10: selbsterstellte Aggregatfunktionen (Standardabweichung, Median) Eine 'Aggregatfunktion' wird auf die abgefragte Datensequenz angewendet und berechnet aus allen darin enthaltenen Daten einen einzelnen Kennwert (Singleton). Die Sequenzvariable des Typs 'IEnumerable' ist um eine Reihe von Standard-Aggregatfunktionen erweitert, die aber bei IEEE-Variablen die Sonderwerte nicht in allen Fällen sinnvoll verarbeiten. Zweckmäßiger ist es deshalb, eigene Funktionen für die Aggregate-Klausel bei LINQ-Abfragen zu erstellen. Die Beispiel-Funktion 'SDEV' berechnet die Daten-Standardabweichung (N-Gewichtung) der Elemente ein- oder mehrdimensionaler Arrays eines beliebigen numerischen Datentyps, die Funktion 'Median' den Medianwert der Daten. Die Erweiterung 'Count_Missings' gibt die Zahl der Array-Elemente an, die IEEE-Sonderwerte enthalten. 'Count_Range' gibt die Zahl der Elemente an, die innerhalb eines vorgegebenen Werte-Intervalls liegen. Um die Funktionen für alle numerischen 'Primitives' verfügbar zu machen, ist die Hilfsfunktion 'ToDoubleArray' erstellt worden. Sie filtert unplausible IEEE-Werte und benutzt eine Methode der System.Convert-Klasse zur Konvertierung. Die Berechnungen werden deshalb stets mit Double-Werten durchgeführt. Falls eine Funktion scheitert, wird 'NaN' bzw. -1 zurückgegeben. Die Erweiterungen sind auch direkt auf eindimensionale numerische Arrays anwendbar (vgl. Aufruf-Beispiel). Diese 'Extensions' werden auch für Variablen des Typs 'System.String' (=Character-Array) von der VB-Intellisense angezeigt (Werttyp). Einfach ignorieren! Die Convert-Methode löst beim Datentyp 'Char' immer eine Ausnahme aus. Im Aufruf-Beispiel werden durch die Erweiterung 'ToQuery' alle Elemente eines zweidimensionalen Single-Arrays in eine Sequenz eingetragen, die dann durch 'SDEV' bzw. 'Median' aggregiert wird. Die hier vorgeschlagene Variante unterscheidet sich von der in der VB-Dokumentation (Modul 'UserDefinedAggregates'). Soweit ich beurteilen kann, ist das dortige Beispiel (Aufruf einer Selector-Funktion) nur in LINQ-Abfragen, aber nicht als Erweiterung für Arrays verwendbar (Ausnahme: Datentyp Double, Direktaufruf). Option Strict On Option Explicit On Option Infer Off Imports Microsoft.VisualBasic.Information 'IsNothing Imports System.Collections.Generic Imports System.Linq.Enumerable ''' <summary> ''' Modul mit Aggragatfunktion für LINQ-Aggregate</summary> Module modLinqArray_Statistik ''' <summary>Berechnung der Standardabweichung der ''' gültigen numerischen Werte in der Auflistung ''' </summary> ''' <param name="enm_data">Auflistung</param> ''' <returns>Standardabweichung (oder NaN!)</returns> <Runtime.CompilerServices.Extension()> _ Public Function SDEV(Of T As Structure) (ByVal enm_data As IEnumerable(Of T)) As Double Try ' Auswahl der gültigen Werte ' incl. Umwandlung in Double-Werte Dim iarr() As Double = ToDoubleArray(enm_data) If IsNothing(iarr) Then Return Double.NaN Dim N As Integer = iarr.Length If N < 1 Then Return Double.NaN ' Mittelwert der gültigen Daten ' durch Erweiterung von 'Enumerable' Dim mw As Double = iarr.Average ' Summe der Abweichungsquadrate ' (starkes Anwachsen der Summe vermeiden) Dim sum As Double = 0 For Each elem As Double In iarr sum += ((elem - mw) ^ 2 / N) Next elem ' Rückgabe der Standardabweichung Return Math.Sqrt(sum) Catch ex As Exception Return Double.NaN End Try End Function ''' <summary>Berechnung des Medianwertes der ''' gültigen numerischen Werte in der Auflistung ''' </summary> ''' <param name="enm_data">Auflistung</param> ''' <returns>Median (oder NaN!)</returns> <Runtime.CompilerServices.Extension()> _ Public Function Median(Of T As Structure) (ByVal enm_data As IEnumerable(Of T)) As Double Dim iarr() As Double = ToDoubleArray(enm_data) If IsNothing(iarr) Then Return Double.NaN If iarr.Length = 0 Then Return Double.NaN ' gültige Daten sortieren Array.Sort(iarr) ' mittleren Index ermitteln Dim mitte As Integer = iarr.Length \ 2 ' Übliche Konvention für Medianberechnung If iarr.Length Mod 2 = 0 Then Return (iarr(mitte) + iarr(mitte - 1)) / 2 Else Return iarr(mitte) End If End Function ''' <summary>Anzahl der ungültigen/fehlenden ''' numerischen Werte in der Auflistung</summary> ''' <param name="enm_data">Auflistung</param> ''' <returns>Anzahl ungültiger Werte (oder -1)</returns> <Runtime.CompilerServices.Extension()> _ Public Function Count_Missings(Of T As Structure) (ByVal enm_data As IEnumerable(Of T)) As Integer ' Auswahl der gültigen Werte ' incl. Umwandlung in Double-Werte Dim N As Integer = enm_data.Count Dim iarr() As Double = ToDoubleArray(enm_data) If IsNothing(iarr) Then Return -1 Return N - iarr.Length End Function ''' <summary>Anzahl der Werte innerhalb eines Bereichs ''' </summary> ''' <typeparam name="T">Datentyp</typeparam> ''' <param name="enm_data">Auflistung</param> ''' <param name="Range_Minimum">Untergrenze des Bereichs</param> ''' <param name="Range_Maximum">Obergrenze des Bereichs</param> ''' <returns>Anzahl im Bereich (oder -1)</returns> <Runtime.CompilerServices.Extension()> _ Public Function Count_Range(Of T As Structure) (ByVal enm_data As IEnumerable(Of T), _ ByVal Range_Minimum As T, ByVal Range_Maximum As T) As Integer Const minuseins As Integer = -1 Try Dim Range_Mini As Double = Convert.ToDouble(Range_Minimum) Dim Range_Maxi As Double = Convert.ToDouble(Range_Maximum) If Range_Mini > Range_Maxi Then Return minuseins ' Auswahl der gültigen Werte ' incl. Umwandlung in Double-Werte Return (ToDoubleArray(enm_data, Range_Mini, Range_Maxi).Length) Catch Return minuseins End Try End Function ''' <summary> ''' Filtert und Konvertiert eine Auflistung</summary> ''' <param name="enm_data">Auflistung</param> ''' <returns>Double-Array (oder Nothing)</returns> Private Function ToDoubleArray(Of T As Structure) (ByVal enm_data As IEnumerable(Of T), _ Optional ByVal MinVal As Double = Double.MinValue, _ Optional ByVal MaxVal As Double = Double.MaxValue) As Double() Try Return (From x In enm_data _ Let dbl As Double = Convert.ToDouble(x) _ Where dbl >= MinVal And dbl <= MaxVal _ Select dbl).ToArray Catch Return Nothing End Try End Function End Module Option Strict On Option Explicit On Option Infer Off Imports System.Collections.Generic Imports System.Linq.Enumerable Module modLinqArray_Exempl10 ''' <summary>Beispiele zur Verwendung von ''' selbsterstellten Aggregatfunktionen</summary> Public Sub Ex10() ' Zweidimensionales Array mit einigen NaN-Werten Dim sng_arr2(99, 3) As Single For i As Integer = 0 To 99 For k As Integer = 0 To 3 sng_arr2(i, k) = CSng(i) If i Mod 5 = 0 Then sng_arr2(i, k) = Single.NaN End If Next k, i ' Berechnung der Standardabweichung ' der gültigen Werte > 50 im Array durch die ' Aggregatfunktion 'SDev' Dim sd As Double = _ Aggregate range_var As Single In sng_arr2.ToQuery _ Where range_var > 50 Into SDEV() ' Berechnung des Median ' der gültigen Werte <= 50 im Array durch die ' Aggregatfunktion 'Median' Dim med As Double = _ Aggregate range_var As Single In sng_arr2.ToQuery _ Where range_var <= 50 Into Median() ' Eindimensionales Array Dim sng_arr(99) As Single For i As Integer = 0 To 99 sng_arr(i) = CSng(Rand()) Next i ' Die Aggregate-Funktionen operieren auch als ' Erweiterungen eindimensionaler Arrays Dim sd_arr As Double = sng_arr.SDEV Dim med_arr As Double = sng_arr.Median Dim cr_arr As Integer = sng_arr.Count_Range(0.2, 0.7) End Sub End Module Beispiel 11: Nutzung der IQueryable-Schnittstelle Für die Abfrage von Daten in Arrays wird die (generische) IQueryable-Schnittstelle nicht benötigt. Man kann sie aber aus der IEnumerable-Schnittstelle per Transformation (Erweiterung 'AsQueryable') erhalten. Die Sequenz ist dann kompatibel zu 'Queries', die auf externe Datenquellen verweisen. Die Schnittstelle 'IQueryable' ist NICHT im Namespace System.Collections.Generic' angesiedelt, sondern im Namespace 'System.Linq'. Ihre Erweiterungsmethoden im Namespace 'System.Linq.Queryable' entsprechen weitgehend denen für die 'IEnumerable'-Schnittstelle, die in 'System.Linq.Enumerable' vereinbart sind. Vieles ist geerbt. Es wird gezeigt, wie die Datensequenz durch Verwendung des Enumerators (Methode 'MoveNext', schreibgeschützte Eigenschaft 'Current') durchlaufen werden kann. Die Reset-Methode erlaubt den Neubeginn der Iteration. Option Strict On Option Explicit On Option Infer Off Imports Microsoft.VisualBasic.Strings 'Format, Replace Imports System.Collections.Generic Imports System.Linq.Enumerable Imports System.Linq.Queryable Module modLinqArray_Exempl11 '''<summary> '''Sequenzvariable über IQueryable-Schnittstelle</summary> Public Sub Ex11() Dim str_arr(99) As String For i As Integer = 0 To 99 str_arr(i) = "Element " + Format(i, "00") Next i Dim seq_quer As Linq.IQueryable(Of String) = _ (From str As String In str_arr _ Let newstr As String = Replace(str, "Element", "-->") _ Order By newstr Descending Select newstr).AsQueryable ' System.Linq.EnumerableQuery`1[System.String] Dim typ_quer As String = seq_quer.GetType.ToString ' IQueryable erbt den Enumerator von IEnumerable Dim enm_quer As IEnumerator(Of String) = _ seq_quer.GetEnumerator() Dim el_quer As String While enm_quer.MoveNext() el_quer = enm_quer.Current End While End Sub End Module Beispiel 12: Direkte Verwendung der Enumeratoren Bei der Verwendung der Enumeratoren ist zu beachten, dass mit der 'MoveNext'-Methode begonnen werden muss, weil die Eigenschaft 'Current' sonst noch keinen sinnvollen Wert enthält. Die Methode 'Reset' setzt den Enumerator auf den Anfang zurück. Es ist nur die einfache Iteration der Elemente eines Array oder einer Query möglich. Um die aktuelle Position in der Datensequenz zu erhalten, muss eine Zählvariable mitgeführt werden. Das Modul 'modLinqArray_Help' wird benötigt. Die Erweiterung 'EnumToQuery' trägt Array-Elemente in eine Sequenz ein und ist nicht auf Werttypen beschränkt. Option Strict On Option Explicit On Option Infer Off Imports System.Collections Imports System.Collections.Generic Module modLinqArray_Exempl12 ''' <summary> ''' Verwendung der Enumeratoren: System.Collections ''' und System.Collections.Generic</summary> Public Sub Ex12() Dim sng_arr(99) As Single For i As Integer = 0 To 99 sng_arr(i) = CSng(Rand()) + CSng(i) Next i ' generische Schnittstelle (Werttyp) Dim enm_sng As IEnumerator(Of Single) = _ sng_arr.ToQuery.GetEnumerator Dim sng As Single Dim z As Integer = -1 ' Zählvariable/null-basiert With enm_sng While .MoveNext z += 1 sng = .Current End While End With Dim arr_str(99) As String For i As Integer = 0 To 99 arr_str(i) = "Element " + Microsoft.VisualBasic.Format(i, "000") Next i ' generische Schnittstelle Dim enm_str As IEnumerator(Of String) = _ arr_str.EnumToQuery.GetEnumerator Dim str As String With enm_str While .MoveNext str = .Current End While End With ' nicht-generische Schnittstelle Dim enm_arr As IEnumerator = arr_str.GetEnumerator With enm_arr While .MoveNext str = CStr(.Current) End While End With Dim obj_arr(99) As Object For i As Integer = 0 To 99 obj_arr(i) = New cData(Rand, i) Next i ' generische Schnittstelle Dim enm_obj As IEnumerator(Of Object) = _ obj_arr.EnumToQuery.GetEnumerator Dim dt As cData With enm_obj While .MoveNext dt = CType(.Current, cData) End While End With End Sub End Module Dieser Workshop wurde bereits 13.517 mal aufgerufen.
Anzeige
Diesen und auch alle anderen Workshops finden Sie auch auf unserer aktuellen vb@rchiv Vol.6 (einschl. Beispielprojekt!) 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. |
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. Tipp des Monats Dezemeber 2024 Roland Wutzke MultiSort im ListView-Control Dieses Beispiel zeigt, wie sich verschiedene Sortierfunktionen für ein ListView Control realisieren lassen. Access-Tools Vol.1 Über 400 MByte Inhalt Mehr als 250 Access-Beispiele, 25 Add-Ins und ActiveX-Komponenten, 16 VB-Projekt inkl. Source, mehr als 320 Tipps & Tricks für Access und VB |
|||||||||||||
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. |