Die Community zu .NET und Classic VB.
Menü

Tipp-Upload: VB.NET 0380: LINQ-Abfragen benutzerdefiniert erweitern

 von 

Über den Tipp  

Dieser Tippvorschlag ist noch unbewertet.

Der Vorschlag ist in den folgenden Kategorien zu finden:

  • Sprachmerkmale

Dem Tippvorschlag wurden folgende Schlüsselwörter zugeordnet:
linq, linq provider, linq-provider, monad, functional programming, funktional, maybe, option

Der Vorschlag wurde erstellt am: 07.11.2009 14:23.
Die letzte Aktualisierung erfolgte am 07.11.2009 14:23.

Zurück zur Übersicht

Beschreibung  

Beim Thema Linq denkt man zunächst immer nur an SQL-artige Abfragen von Datenbanken oder Objekten. Tätsächlich ist Linq aber nur eine Abfragesyntax, deren Inhalt über sog. "Linq provider", das sind Module mit Erweiterungsmethoden, komplett selbst bestimmt werden kann. Die Datenbank- und Listenverarbeitung ist dabei nur eine mögliche Fähigkeit von Linq.

Tätsächlich kann man diese Abfragen aber auf beliebige Typen erweitern und damit von parallelen Abfragen über Continuations, nicht-deterministischen Berechnungen bis hin zu Mini-Programmiersprachen und Parsern Berechnungsschemata aus verschiedensten Gebieten kurz und bündig notieren.

Zum Ablauf: Beim Kompilieren wird eine Linq-Abfrage in Aufrufe entsprechender Extension-Methods (Select, SelectMany, Where, Skip) umgewandelt, die wir selbst erstellen können. Benötigt sind dabei lediglich die Funktionen Select und SelectMany, alles andere ist optional. Grundlage aller dieser Berechnungen sind dabei zwei Funktionen namens Return und Bind, auf denen sich die anderen herleiten. Return gibt einen Wert aus der Abfrage zurück, Bind kombiniert zwei Teile hintereinander.

Dieser Tipp zeigt ein Beispiel für solche benutzerdefinierten Linq-Abfragen, indem er zunächst eine Klasse für Berechnungen stellt, die fehlschlagen können (Vergleichbar mit den Nullable Values). Für diese wird dann ein Linq-Provider erstellt, der uns erlaubt, solche Berechnungen angenehm zu verknüpfen. Die gesamte Berechnung schlägt dabei sofort fehl, sobald es eine Teilberechnung tut. Den gleichen Effekt ohne Linq zu erzielen würde sehr komplizierten Code mit zahlreichen If's und sogar Sprungmarken nach sich ziehen. Auch wenn die Implementierung des Providers kompliziert aussieht, ist die Ergebnis-Abfrage extrem verständlich und wiederverwendbar.

Schwierigkeitsgrad

Schwierigkeitsgrad 3

Verwendete API-Aufrufe:

Download:

Download des Beispielprojektes [9,34 KB]

' Dieser Source stammt von http://www.activevb.de
' und kann frei verwendet werden. Für eventuelle Schäden
' wird nicht gehaftet.

' Um Fehler oder Fragen zu klären, nutzen Sie bitte unser Forum.
' Ansonsten viel Spaß und Erfolg mit diesem Source!
'
' Beachten Sie, das vom Designer generierter Code hier ausgeblendet wird.
' In den Zip-Dateien ist er jedoch zu finden.

' --------- Anfang Projektgruppe Linq providers.sln  ---------
' -------- Anfang Projektdatei Linq providers.vbproj  --------
' ------------------ Anfang Datei Maybe.vb  ------------------
Option Strict On

' Maybe-Klasse und die Konstruktorfunktionen Some und None bereitstellen

Class Maybe(Of T)

    Private ReadOnly m_IsEmpty As Boolean
    Private ReadOnly m_Value As T

    Public Sub New()

        m_IsEmpty = True
        m_Value = Nothing

    End Sub

    Public Sub New(ByVal Value As T)

        m_IsEmpty = False
        m_Value = Value

    End Sub

    Public ReadOnly Property IsEmpty() As Boolean
        Get
            Return m_IsEmpty

        End Get

    End Property

    Public ReadOnly Property Value() As T
        Get
            Return m_Value

        End Get

    End Property

End Class

<HideModuleName()> Module MaybeHelpers

    Function Some(Of T)(ByVal Value As T) As Maybe(Of T)

        Return New Maybe(Of T)(Value)

    End Function

    Function None(Of T)() As Maybe(Of T)

        Return New Maybe(Of T)()

    End Function

End Module

' ------------------- Ende Datei Maybe.vb  -------------------
' ------------ Anfang Datei MaybeLinqProvider.vb  ------------
Option Strict On

Imports System.Runtime.CompilerServices

' Linq-Abfragefunktionen bereitstellen

<HideModuleName()> Module MaybeLinqProvider

    ' Grundlage jeder Linq-Berechnung sind die beiden Methoden Return und Bind
    ' Die weiter unten stehenden Definitione bauen auf diesen auf und dienen nur der
    ' Anbindung an die .NET-Konventionen
    ' Sie können einfach kopiert werden

    ' Einen Wert zurückgeben
    Private Function [Return](Of A)(ByVal Value As A) As Maybe(Of A)

        Return Some(Value)

    End Function

    ' Zwei Berechnungen kombinieren
    <Extension()> Private Function Bind(Of A, B)(ByVal Expr As Maybe(Of A), ByVal Func As _
        Func(Of A, Maybe(Of B))) As Maybe(Of B)

        ' Wenn die Berechnung erfolgreich war, mit der nächsten fortfahren
        If Not Expr.IsEmpty Then
            Return Func(Expr.Value)

        Else

            ' Teilberechnung ist fehlgeschlagen - Die gesamte schlägt daher auch fehl
            Return None(Of B)()
        End If

    End Function

    ' Standard-Implementierungen der Linq-Abfragefunktionen
    <Extension()> Function [Select](Of A, B)(ByVal Expr As Maybe(Of A), ByVal Func As Func(Of _
        A, B)) As Maybe(Of B)

        Return Expr.Bind(Function(x) [Return](Func(x)))

    End Function

    <Extension()> _
        Function SelectMany(Of A, B, C)(ByVal Expr As Maybe(Of A), _
                                        ByVal Func As Func(Of A, Maybe(Of B)), _
                                        ByVal Sel As Func(Of A, B, C)) As Maybe(Of C)

        Return Expr.Bind(Function(x) Func(x).Select(Function(y) Sel(x, y)))

    End Function

    <Extension()> _
        Function Where(Of A)(ByVal Expr As Maybe(Of A), _
                             ByVal Predicate As Predicate(Of A)) As Maybe(Of A)

        Return Expr.Bind(Function(x) If(Predicate(x), [Return](x), None(Of A)()))

    End Function

End Module

' ------------- Ende Datei MaybeLinqProvider.vb  -------------
' ----------------- Anfang Datei Module1.vb  -----------------

Module Module1

    Structure Person

        Public Name As String
        Public City As String
        Public Size As Double

    End Structure

    Function ReadNumber(ByVal Prompt As String) As Maybe(Of Double)

        Console.Write("{0}: ", Prompt)

        Dim Input = Console.ReadLine
        Dim Result As Double

        If Double.TryParse(Input, Result) Then
            Return Some(Result)

        Else

            Return None(Of Double)()
        End If

    End Function

    Function ReadString(ByVal Prompt As String) As Maybe(Of String)

        Console.Write("{0}: ", Prompt)

        Dim Input = Console.ReadLine

        Return If(Input.Trim <> "", Some(Input), None(Of String))

    End Function

    Sub Main()

        Do
            Console.WriteLine("Dateneingabe: ")

            ' Extrem einfache Datenabfrage
            Dim Person = From Name In ReadString("Dein Name") From Size In ReadNumber( _
                "Deine Größe") Where Size >= 0.0 From City In ReadString("Dein Wohnort") _
                Select New Person() With {.Name = Name, .Size = Size, .City = City}

            If Person.IsEmpty Then
                Console.WriteLine("Deine Eingabe war leider ungültig")
                Console.WriteLine()

            Else

                Console.WriteLine("Hallo, {0}", Person.Value.Name)

                Exit Do

            End If

        Loop

        Console.ReadKey()

    End Sub

End Module

' ------------------ Ende Datei Module1.vb  ------------------
' --------- Ende Projektdatei Linq providers.vbproj  ---------
' ---------- Ende Projektgruppe Linq providers.sln  ----------

	

Diskussion  

Diese Funktion ermöglicht es, Fragen, die die Veröffentlichung des Tipps betreffen, zu klären, oder Anregungen und Verbesserungsvorschläge einzubringen. Nach der Veröffentlichung des Tipps werden diese Beiträge nicht weiter verlinkt. Allgemeine Fragen zum Inhalt sollten daher hier nicht geklärt werden.
Folgende Diskussionen existieren bereits

LINQ 2.0 - Dario 10.12.2009 18:19

Um eine Diskussion eröffnen zu können, müssen sie angemeldet sein.