Tipp-Upload: VB.NET 0380: LINQ-Abfragen benutzerdefiniert erweitern
von Dario
Ü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.
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 |
Verwendete API-Aufrufe: |
Download: |
' 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
Um eine Diskussion eröffnen zu können, müssen sie angemeldet sein.