Dela via


Självstudie: Skapa en typprovider

Typprovidermekanismen i F# är en viktig del av dess stöd för informationsrik programmering. Den här självstudien beskriver hur du skapar egna typprovidrar genom att gå igenom utvecklingen av flera enkla typproviders för att illustrera de grundläggande begreppen. Mer information om typprovidermekanismen i F#finns i Typprovidrar.

F#-ekosystemet innehåller en rad typer av leverantörer för vanliga Internet- och företagsdatatjänster. Till exempel:

  • FSharp.Data innehåller typprovidrar för JSON-, XML-, CSV- och HTML-dokumentformat.

  • SwaggerProvider innehåller två generativa typproviders som genererar objektmodell och HTTP-klienter för API:er som beskrivs av OpenApi 3.0- och Swagger 2.0-scheman.

  • FSharp.Data.SqlClient har en uppsättning typprovidrar för kompileringstidskontrollerad inbäddning av T-SQL i F#.

Du kan skapa leverantörer av anpassade typer, eller så kan du referera till typprovidrar som andra har skapat. Din organisation kan till exempel ha en datatjänst som ger ett stort och växande antal namngivna datauppsättningar, var och en med ett eget stabilt dataschema. Du kan skapa en typprovider som läser schemana och visar de aktuella datauppsättningarna för programmeraren på ett starkt skrivet sätt.

Innan du börjar

Typprovidermekanismen är främst utformad för att mata in stabila data- och tjänstinformationsutrymmen i F#-programmeringsmiljön.

Den här mekanismen är inte utformad för att mata in informationsutrymmen vars schema ändras under programkörningen på sätt som är relevanta för programlogik. Dessutom är mekanismen inte utformad för metaprogrammering på intraspråk, även om den domänen innehåller vissa giltiga användningsområden. Du bör endast använda den här mekanismen när det behövs och där utvecklingen av en typprovider ger mycket högt värde.

Du bör undvika att skriva en typprovider där ett schema inte är tillgängligt. På samma sätt bör du undvika att skriva en typprovider där ett vanligt (eller till och med ett befintligt) .NET-bibliotek räcker.

Innan du börjar kan du ställa följande frågor:

  • Har du ett schema för din informationskälla? I så fall, vad är mappningen till systemet av typen F# och .NET?

  • Kan du använda ett befintligt (dynamiskt skrivet) API som utgångspunkt för implementeringen?

  • Kommer du och din organisation att ha tillräckligt med användning av typprovidern för att göra det värt att skriva? Skulle ett normalt .NET-bibliotek uppfylla dina behov?

  • Hur mycket kommer ditt schema att ändras?

  • Kommer den att ändras under kodningen?

  • Kommer det att ändras mellan kodningssessioner?

  • Kommer den att ändras under programkörningen?

Typprovidrar passar bäst för situationer där schemat är stabilt vid körning och under den kompilerade kodens livslängd.

En enkel typprovider

Det här exemplet är Samples.HelloWorldTypeProvider, som liknar exemplen examples i katalogen för provider-SDK för F#-typ. Providern gör ett "typutrymme" tillgängligt som innehåller 100 raderade typer, som följande kod visar med hjälp av F#-signatursyntaxen och utelämnar informationen för alla utom Type1. Mer information om raderingstyper finns i Information om borttagna typer senare i det här avsnittet.

namespace Samples.HelloWorldTypeProvider

type Type1 =
    /// This is a static property.
    static member StaticProperty : string

    /// This constructor takes no arguments.
    new : unit -> Type1

    /// This constructor takes one argument.
    new : data:string -> Type1

    /// This is an instance property.
    member InstanceProperty : int

    /// This is an instance method.
    member InstanceMethod : x:int -> char

    nested type NestedType =
        /// This is StaticProperty1 on NestedType.
        static member StaticProperty1 : string
        …
        /// This is StaticProperty100 on NestedType.
        static member StaticProperty100 : string

type Type2 =
…
…

type Type100 =
…

Observera att uppsättningen med typer och medlemmar som tillhandahålls är statiskt känd. Det här exemplet utnyttjar inte möjligheten för leverantörer att tillhandahålla typer som är beroende av ett schema. Implementeringen av typprovidern beskrivs i följande kod och informationen beskrivs i senare avsnitt i det här avsnittet.

Varning

Det kan finnas skillnader mellan den här koden och onlineexemplen.

namespace Samples.FSharp.HelloWorldTypeProvider

open System
open System.Reflection
open ProviderImplementation.ProvidedTypes
open FSharp.Core.CompilerServices
open FSharp.Quotations

// This type defines the type provider. When compiled to a DLL, it can be added
// as a reference to an F# command-line compilation, script, or project.
[<TypeProvider>]
type SampleTypeProvider(config: TypeProviderConfig) as this =

  // Inheriting from this type provides implementations of ITypeProvider
  // in terms of the provided types below.
  inherit TypeProviderForNamespaces(config)

  let namespaceName = "Samples.HelloWorldTypeProvider"
  let thisAssembly = Assembly.GetExecutingAssembly()

  // Make one provided type, called TypeN.
  let makeOneProvidedType (n:int) =
  …
  // Now generate 100 types
  let types = [ for i in 1 .. 100 -> makeOneProvidedType i ]

  // And add them to the namespace
  do this.AddNamespace(namespaceName, types)

[<assembly:TypeProviderAssembly>]
do()

Om du vill använda den här providern öppnar du en separat instans av Visual Studio, skapar ett F#-skript och lägger sedan till en referens till providern från skriptet med hjälp av #r som följande kod visar:

#r @".\bin\Debug\Samples.HelloWorldTypeProvider.dll"

let obj1 = Samples.HelloWorldTypeProvider.Type1("some data")

let obj2 = Samples.HelloWorldTypeProvider.Type1("some other data")

obj1.InstanceProperty
obj2.InstanceProperty

[ for index in 0 .. obj1.InstanceProperty-1 -> obj1.InstanceMethod(index) ]
[ for index in 0 .. obj2.InstanceProperty-1 -> obj2.InstanceMethod(index) ]

let data1 = Samples.HelloWorldTypeProvider.Type1.NestedType.StaticProperty35

Leta sedan efter typerna under namnområdet Samples.HelloWorldTypeProvider som typprovidern genererade.

Innan du kompilera om providern kontrollerar du att du har stängt alla instanser av Visual Studio och F# Interactive som använder providerns DLL. Annars uppstår ett byggfel eftersom DLL-filen för utdata låses.

Om du vill felsöka den här providern med hjälp av utskriftsuttryck skapar du ett skript som exponerar ett problem med providern och använder sedan följande kod:

fsc.exe -r:bin\Debug\HelloWorldTypeProvider.dll script.fsx

Om du vill felsöka den här providern med hjälp av Visual Studio öppnar du kommandotolken För utvecklare för Visual Studio med administrativa autentiseringsuppgifter och kör följande kommando:

devenv.exe /debugexe fsc.exe -r:bin\Debug\HelloWorldTypeProvider.dll script.fsx

Alternativt öppnar du Visual Studio, öppnar felsökningsmenyn, väljer Debug/Attach to process…och ansluter till en annan devenv process där du redigerar skriptet. Med den här metoden kan du enklare rikta in viss logik i typprovidern genom att interaktivt skriva uttryck i den andra instansen (med fullständig IntelliSense och andra funktioner).

Du kan inaktivera felsökning av Just My Code för att bättre identifiera fel i genererad kod. Information om hur du aktiverar eller inaktiverar den här funktionen finns i Navigera genom Kod med felsökningsprogrammet. Du kan också ange undantagsfångst från första chansen genom att öppna Debug menyn och sedan välja Exceptions eller genom att välja Ctrl+Alt+E-tangenterna Exceptions för att öppna dialogrutan. I den dialogrutan, under Common Language Runtime Exceptions, markerar du kryssrutan Thrown .

Implementering av typprovidern

I det här avsnittet går vi igenom huvudavsnitten i typproviderimplementeringen. Först definierar du typen för själva providern av anpassad typ:

[<TypeProvider>]
type SampleTypeProvider(config: TypeProviderConfig) as this =

Den här typen måste vara offentlig och du måste markera den med attributet TypeProvider så att kompilatorn känner igen typprovidern när ett separat F#-projekt refererar till den sammansättning som innehåller typen. Konfigurationsparametern är valfri och innehåller, om den finns, sammanhangsbaserad konfigurationsinformation för typproviderinstansen som F#-kompilatorn skapar.

Sedan implementerar du gränssnittet ITypeProvider . I det här fallet använder TypeProviderForNamespaces du typen från API:et ProvidedTypes som bastyp. Den här hjälptypen kan ge en begränsad samling ivrigt tillhandahållna namnområden, som var och en direkt innehåller ett begränsat antal fasta, ivrigt tillhandahållna typer. I det här sammanhanget genererar providern ivrigt typer även om de inte behövs eller används.

inherit TypeProviderForNamespaces(config)

Definiera sedan lokala privata värden som anger namnområdet för de angivna typerna och hitta själva typprovidersammansättningen. Den här sammansättningen används senare som den logiska överordnade typen för de borttagna typerna som tillhandahålls.

let namespaceName = "Samples.HelloWorldTypeProvider"
let thisAssembly = Assembly.GetExecutingAssembly()

Skapa sedan en funktion för att ange var och en av typerna Type1... Typ 100. Den här funktionen förklaras mer detaljerat senare i det här avsnittet.

let makeOneProvidedType (n:int) = …

Generera sedan de 100 angivna typerna:

let types = [ for i in 1 .. 100 -> makeOneProvidedType i ]

Lägg sedan till typerna som ett angivet namnområde:

do this.AddNamespace(namespaceName, types)

Lägg slutligen till ett sammansättningsattribut som anger att du skapar en typprovider-DLL:

[<assembly:TypeProviderAssembly>]
do()

Tillhandahålla en typ och dess medlemmar

Funktionen makeOneProvidedType utför det verkliga arbetet med att tillhandahålla en av typerna.

let makeOneProvidedType (n:int) =
…

Det här steget förklarar implementeringen av den här funktionen. Skapa först den angivna typen (till exempel Type1, när n = 1 eller Type57, när n = 57).

// This is the provided type. It is an erased provided type and, in compiled code,
// will appear as type 'obj'.
let t = ProvidedTypeDefinition(thisAssembly, namespaceName,
                               "Type" + string n,
                               baseType = Some typeof<obj>)

Observera följande punkter:

  • Den angivna typen raderas. Eftersom du anger att bastypen är objvisas instanser som värden av typen obj i kompilerad kod.

  • När du anger en icke-kapslad typ måste du ange sammansättning och namnrymd. För borttagna typer bör sammansättningen vara själva typprovidersammansättningen.

Lägg sedan till XML-dokumentation till typen. Den här dokumentationen fördröjs, dvs. beräknas på begäran om värdkompilatorn behöver den.

t.AddXmlDocDelayed (fun () -> $"""This provided type {"Type" + string n}""")

Sedan lägger du till en angivet statisk egenskap till typen:

let staticProp = ProvidedProperty(propertyName = "StaticProperty",
                                  propertyType = typeof<string>,
                                  isStatic = true,
                                  getterCode = (fun args -> <@@ "Hello!" @@>))

Om du hämtar den här egenskapen utvärderas alltid strängen "Hello!". För GetterCode egenskapen används en F#-offert som representerar koden som värdkompilatorn genererar för att hämta egenskapen. Mer information om citattecken finns i Kodofferter (F#).

Lägg till XML-dokumentation i egenskapen.

staticProp.AddXmlDocDelayed(fun () -> "This is a static property")

Koppla nu den angivna egenskapen till den angivna typen. Du måste koppla en angivet medlem till en och endast en typ. Annars kommer medlemmen aldrig att vara tillgänglig.

t.AddMember staticProp

Skapa nu en tillhandahållen konstruktor som inte tar några parametrar.

let ctor = ProvidedConstructor(parameters = [ ],
                               invokeCode = (fun args -> <@@ "The object data" :> obj @@>))

InvokeCode För konstruktorn returnerar en F#-offert, som representerar den kod som värdkompilatorn genererar när konstruktorn anropas. Du kan till exempel använda följande konstruktor:

new Type10()

En instans av den angivna typen skapas med underliggande data "Objektdata". Den citerade koden innehåller en konvertering till obj eftersom den typen är radering av den angivna typen (som du angav när du deklarerade den angivna typen).

Lägg till XML-dokumentation i konstruktorn och lägg till den angivna konstruktorn i den angivna typen:

ctor.AddXmlDocDelayed(fun () -> "This is a constructor")

t.AddMember ctor

Skapa en andra tillhandahållen konstruktor som tar en parameter:

let ctor2 =
ProvidedConstructor(parameters = [ ProvidedParameter("data",typeof<string>) ],
                    invokeCode = (fun args -> <@@ (%%(args[0]) : string) :> obj @@>))

InvokeCode För konstruktorn returneras återigen en F#-offert, som representerar koden som värdkompilatorn genererade för ett anrop till metoden. Du kan till exempel använda följande konstruktor:

new Type10("ten")

En instans av den angivna typen skapas med underliggande data "tio". Du kanske redan har märkt att InvokeCode funktionen returnerar en offert. Indata till den här funktionen är en lista med uttryck, en per konstruktorparameter. I det här fallet är ett uttryck som representerar det enskilda parametervärdet tillgängligt i args[0]. Koden för ett anrop till konstruktorn tvingar returvärdet till den borttagna typen obj. När du har lagt till den andra angivna konstruktorn i typen skapar du en angivet instansegenskap:

let instanceProp =
    ProvidedProperty(propertyName = "InstanceProperty",
                     propertyType = typeof<int>,
                     getterCode= (fun args ->
                        <@@ ((%%(args[0]) : obj) :?> string).Length @@>))
instanceProp.AddXmlDocDelayed(fun () -> "This is an instance property")
t.AddMember instanceProp

Om du hämtar den här egenskapen returneras längden på strängen, som är representationsobjektet. Egenskapen GetterCode returnerar en F#-offert som anger den kod som värdkompilatorn genererar för att hämta egenskapen. GetterCode Precis som InvokeCodereturnerar funktionen en offert. Värdkompilatorn anropar den här funktionen med en lista med argument. I det här fallet innehåller argumenten bara det enda uttryck som representerar den instans där getter anropas, som du kan komma åt med hjälp args[0]av . Implementeringen av GetterCode splices sedan till resultatcitaten vid den borttagna typen obj, och en gjutning används för att uppfylla kompilatorns mekanism för att kontrollera typer av att objektet är en sträng. Nästa del av makeOneProvidedType innehåller en instansmetod med en parameter.

let instanceMeth =
    ProvidedMethod(methodName = "InstanceMethod",
                   parameters = [ProvidedParameter("x",typeof<int>)],
                   returnType = typeof<char>,
                   invokeCode = (fun args ->
                       <@@ ((%%(args[0]) : obj) :?> string).Chars(%%(args[1]) : int) @@>))

instanceMeth.AddXmlDocDelayed(fun () -> "This is an instance method")
// Add the instance method to the type.
t.AddMember instanceMeth

Skapa slutligen en kapslad typ som innehåller 100 kapslade egenskaper. Skapandet av den här kapslade typen och dess egenskaper fördröjs, dvs. beräknas på begäran.

t.AddMembersDelayed(fun () ->
  let nestedType = ProvidedTypeDefinition("NestedType", Some typeof<obj>)

  nestedType.AddMembersDelayed (fun () ->
    let staticPropsInNestedType =
      [
          for i in 1 .. 100 ->
              let valueOfTheProperty = "I am string "  + string i

              let p =
                ProvidedProperty(propertyName = "StaticProperty" + string i,
                  propertyType = typeof<string>,
                  isStatic = true,
                  getterCode= (fun args -> <@@ valueOfTheProperty @@>))

              p.AddXmlDocDelayed(fun () ->
                  $"This is StaticProperty{i} on NestedType")

              p
      ]

    staticPropsInNestedType)

  [nestedType])

Information om borttagna typer

Exemplet i det här avsnittet innehåller endast raderade angivna typer, som är särskilt användbara i följande situationer:

  • När du skriver en provider för ett informationsutrymme som endast innehåller data och metoder.

  • När du skriver en provider där korrekta semantik av körningstyp inte är avgörande för praktisk användning av informationsutrymmet.

  • När du skriver en provider för ett informationsutrymme som är så stort och sammankopplat att det inte är tekniskt möjligt att generera verkliga .NET-typer för informationsutrymmet.

I det här exemplet raderas varje angiven typ för att skriva objoch alla användningar av typen visas som typ obj i kompilerad kod. Faktum är att de underliggande objekten i dessa exempel är strängar, men typen visas som System.Object i .NET-kompilerad kod. Precis som med all användning av typradering kan du använda explicit boxning, avboxning och gjutning för att undergräva raderade typer. I det här fallet kan ett gjutet undantag som inte är giltigt resultera när objektet används. En providerkörning kan definiera sin egen privata representationstyp för att skydda mot falska representationer. Du kan inte definiera borttagna typer i själva F# . Endast angivna typer kan raderas. Du måste förstå konsekvenserna, både praktiska och semantiska, av att använda antingen borttagna typer för din typprovider eller en leverantör som tillhandahåller raderade typer. En raderad typ har ingen riktig .NET-typ. Därför kan du inte göra en korrekt reflektion över typen, och du kan subvertera borttagna typer om du använder körningsgjutningar och andra tekniker som förlitar sig på exakt körningstypssemantik. Omstörtning av borttagna typer resulterar ofta i typkonverteringsfel vid körning.

Välja representationer för raderade typer

För vissa användningar av borttagna typer krävs ingen representation. Den borttagna angivna typen kan till exempel endast innehålla statiska egenskaper och medlemmar och inga konstruktorer, och inga metoder eller egenskaper skulle returnera en instans av typen. Om du kan nå instanser av en borttagen typ måste du överväga följande frågor:

Vad är radering av en angivet typ?

  • Radering av en angiven typ är hur typen visas i kompilerad .NET-kod.

  • Radering av en angiven raderad klasstyp är alltid den första oraderade bastypen i arvskedjan för typen.

  • Radering av en angivet raderad gränssnittstyp är alltid System.Object.

Vilka är representationerna av en angivet typ?

  • Uppsättningen möjliga objekt för en raderad angivet typ kallas dess representationer. I exemplet i det här dokumentet är representationerna av alla borttagna angivna typer Type1..Type100 alltid strängobjekt.

Alla representationer av en angivet typ måste vara kompatibla med radering av den angivna typen. (Annars ger antingen F#-kompilatorn ett fel för användning av typprovidern, eller så genereras ej verifierad .NET-kod som inte är giltig. En typprovider är inte giltig om den returnerar kod som ger en representation som inte är giltig.)

Du kan välja en representation för angivna objekt genom att använda någon av följande metoder, som båda är mycket vanliga:

  • Om du bara tillhandahåller en starkt typinskriven omslutning över en befintlig .NET-typ är det ofta bra att din typ raderas till den typen, använder instanser av den typen som representationer eller båda. Den här metoden är lämplig när de flesta av de befintliga metoderna för den typen fortfarande är meningsfulla när du använder den starkt skrivna versionen.

  • Om du vill skapa ett API som skiljer sig avsevärt från alla befintliga .NET-API:er är det klokt att skapa körningstyper som kommer att vara typ radering och representationer för de angivna typerna.

I exemplet i det här dokumentet används strängar som representationer av angivna objekt. Ofta kan det vara lämpligt att använda andra objekt för representationer. Du kan till exempel använda en ordlista som egenskapsväska:

ProvidedConstructor(parameters = [],
    invokeCode= (fun args -> <@@ (new Dictionary<string,obj>()) :> obj @@>))

Alternativt kan du definiera en typ i din typprovider som ska användas vid körning för att bilda representationen, tillsammans med en eller flera körningsåtgärder:

type DataObject() =
    let data = Dictionary<string,obj>()
    member x.RuntimeOperation() = data.Count

Tillhandahållna medlemmar kan sedan skapa instanser av den här objekttypen:

ProvidedConstructor(parameters = [],
    invokeCode= (fun args -> <@@ (new DataObject()) :> obj @@>))

I det här fallet kan du (valfritt) använda den här typen som typ radering genom att ange den här typen som baseType när du skapar ProvidedTypeDefinition:

ProvidedTypeDefinition(…, baseType = Some typeof<DataObject> )
…
ProvidedConstructor(…, InvokeCode = (fun args -> <@@ new DataObject() @@>), …)

Viktiga lektioner

I föregående avsnitt beskrivs hur du skapar en enkel raderingstypprovider som tillhandahåller en mängd olika typer, egenskaper och metoder. I det här avsnittet beskrivs också begreppet typradering, inklusive några av fördelarna och nackdelarna med att tillhandahålla raderade typer från en typprovider och diskuterade representationer av raderade typer.

En typprovider som använder statiska parametrar

Möjligheten att parametrisera typprovidrar med statiska data möjliggör många intressanta scenarier, även om providern inte behöver komma åt lokala eller fjärranslutna data. I det här avsnittet får du lära dig några grundläggande tekniker för att sätta ihop en sådan provider.

Skriv kontrollerad Regex-provider

Anta att du vill implementera en typprovider för reguljära uttryck som omsluter .NET-biblioteken Regex i ett gränssnitt som ger följande kompileringstidsgarantier:

  • Kontrollera om ett reguljärt uttryck är giltigt.

  • Ange namngivna egenskaper för matchningar som baseras på eventuella gruppnamn i det reguljära uttrycket.

Det här avsnittet visar hur du använder typprovidrar för att skapa en RegexTyped typ som mönstret för reguljära uttryck parameteriserar för att ge dessa fördelar. Kompilatorn rapporterar ett fel om det angivna mönstret inte är giltigt och typprovidern kan extrahera grupperna från mönstret så att du kan komma åt dem med hjälp av namngivna egenskaper på matchningar. När du utformar en typprovider bör du överväga hur dess exponerade API ska se ut för slutanvändarna och hur den här designen översätts till .NET-kod. I följande exempel visas hur du använder ett sådant API för att hämta komponenterna i riktkoden:

type T = RegexTyped< @"(?<AreaCode>^\d{3})-(?<PhoneNumber>\d{3}-\d{4}$)">
let reg = T()
let result = T.IsMatch("425-555-2345")
let r = reg.Match("425-555-2345").Group_AreaCode.Value //r equals "425"

I följande exempel visas hur typprovidern översätter dessa anrop:

let reg = new Regex(@"(?<AreaCode>^\d{3})-(?<PhoneNumber>\d{3}-\d{4}$)")
let result = reg.IsMatch("425-123-2345")
let r = reg.Match("425-123-2345").Groups["AreaCode"].Value //r equals "425"

Observera följande:

  • Regex-standardtypen representerar den parametriserade RegexTyped typen.

  • Konstruktorn RegexTyped resulterar i ett anrop till Regex-konstruktorn och skickar in argumentet för statisk typ för mönstret.

  • Resultatet av Match metoden representeras av standardtypen Match .

  • Varje namngiven grupp resulterar i en angivet egenskap, och åtkomst till egenskapen resulterar i att en indexerare används i en matchningssamling Groups .

Följande kod är kärnan i logiken för att implementera en sådan provider, och i det här exemplet utelämnas tillägget av alla medlemmar till den angivna typen. Information om varje tillagd medlem finns i lämpligt avsnitt senare i det här avsnittet.

namespace Samples.FSharp.RegexTypeProvider

open System.Reflection
open Microsoft.FSharp.Core.CompilerServices
open Samples.FSharp.ProvidedTypes
open System.Text.RegularExpressions

[<TypeProvider>]
type public CheckedRegexProvider() as this =
    inherit TypeProviderForNamespaces()

    // Get the assembly and namespace used to house the provided types
    let thisAssembly = Assembly.GetExecutingAssembly()
    let rootNamespace = "Samples.FSharp.RegexTypeProvider"
    let baseTy = typeof<obj>
    let staticParams = [ProvidedStaticParameter("pattern", typeof<string>)]

    let regexTy = ProvidedTypeDefinition(thisAssembly, rootNamespace, "RegexTyped", Some baseTy)

    do regexTy.DefineStaticParameters(
        parameters=staticParams,
        instantiationFunction=(fun typeName parameterValues ->

          match parameterValues with
          | [| :? string as pattern|] ->

            // Create an instance of the regular expression.
            //
            // This will fail with System.ArgumentException if the regular expression is not valid.
            // The exception will escape the type provider and be reported in client code.
            let r = System.Text.RegularExpressions.Regex(pattern)

            // Declare the typed regex provided type.
            // The type erasure of this type is 'obj', even though the representation will always be a Regex
            // This, combined with hiding the object methods, makes the IntelliSense experience simpler.
            let ty =
              ProvidedTypeDefinition(
                thisAssembly,
                rootNamespace,
                typeName,
                baseType = Some baseTy)

            ...

            ty
          | _ -> failwith "unexpected parameter values"))

    do this.AddNamespace(rootNamespace, [regexTy])

[<TypeProviderAssembly>]
do ()

Observera följande:

  • Typprovidern tar två statiska parametrar: pattern, som är obligatorisk och , optionssom är valfria (eftersom ett standardvärde anges).

  • När de statiska argumenten har angetts skapar du en instans av det reguljära uttrycket. Den här instansen utlöser ett undantag om Regex är felaktigt formaterat och det här felet rapporteras till användarna.

  • I återanropet DefineStaticParameters definierar du den typ som ska returneras när argumenten har angetts.

  • Den här koden anges HideObjectMethods till true så att IntelliSense-upplevelsen förblir strömlinjeformad. Det här attributet gör Equalsatt medlemmarna , GetHashCode, Finalizeoch GetType utelämnas från IntelliSense-listor för ett angivet objekt.

  • Du använder obj som bastyp för metoden, men du använder ett Regex objekt som körningsrepresentation av den här typen, som nästa exempel visar.

  • Anropet Regex till konstruktorn genererar ett ArgumentException när ett reguljärt uttryck inte är giltigt. Kompilatorn fångar det här undantaget och rapporterar ett felmeddelande till användaren vid kompileringstillfället eller i Visual Studio-redigeraren. Det här undantaget gör att reguljära uttryck kan verifieras utan att köra ett program.

Den typ som definierats ovan är inte användbar ännu eftersom den inte innehåller några meningsfulla metoder eller egenskaper. Lägg först till en statisk IsMatch metod:

let isMatch =
    ProvidedMethod(
        methodName = "IsMatch",
        parameters = [ProvidedParameter("input", typeof<string>)],
        returnType = typeof<bool>,
        isStatic = true,
        invokeCode = fun args -> <@@ Regex.IsMatch(%%args[0], pattern) @@>)

isMatch.AddXmlDoc "Indicates whether the regular expression finds a match in the specified input string."
ty.AddMember isMatch

Den tidigare koden definierar en metod IsMatch, som tar en sträng som indata och returnerar en bool. Den enda knepiga delen är användningen av args argumentet i InvokeCode definitionen. I det här exemplet args är en lista med citattecken som representerar argumenten för den här metoden. Om metoden är en instansmetod representerar this det första argumentet argumentet. Men för en statisk metod är argumenten bara explicita argument till metoden. Observera att typen av det angivna värdet ska matcha den angivna returtypen (i det här fallet bool). Observera också att den AddXmlDoc här koden använder metoden för att se till att den angivna metoden också har användbar dokumentation som du kan ange via IntelliSense.

Lägg sedan till en instansmatchningsmetod. Den här metoden bör dock returnera ett värde av en angiven Match typ så att grupperna kan nås på ett starkt skrivet sätt. Därför deklarerar Match du först typen. Eftersom den här typen beror på det mönster som angavs som ett statiskt argument måste den här typen kapslas i definitionen av parameteriserad typ:

let matchTy =
    ProvidedTypeDefinition(
        "MatchType",
        baseType = Some baseTy,
        hideObjectMethods = true)

ty.AddMember matchTy

Sedan lägger du till en egenskap i matchningstypen för varje grupp. Vid körning visas en matchning som ett Match värde, så den offert som definierar egenskapen måste använda den Groups indexerade egenskapen för att hämta relevant grupp.

for group in r.GetGroupNames() do
    // Ignore the group named 0, which represents all input.
    if group <> "0" then
    let prop =
      ProvidedProperty(
        propertyName = group,
        propertyType = typeof<Group>,
        getterCode = fun args -> <@@ ((%%args[0]:obj) :?> Match).Groups[group] @@>)
        prop.AddXmlDoc($"""Gets the ""{group}"" group from this match""")
    matchTy.AddMember prop

Observera återigen att du lägger till XML-dokumentation i den angivna egenskapen. Observera också att en egenskap kan läsas om en GetterCode funktion tillhandahålls och egenskapen kan skrivas om en SetterCode funktion tillhandahålls, så den resulterande egenskapen är skrivskyddad.

Nu kan du skapa en instansmetod som returnerar ett värde av den här Match typen:

let matchMethod =
    ProvidedMethod(
        methodName = "Match",
        parameters = [ProvidedParameter("input", typeof<string>)],
        returnType = matchTy,
        invokeCode = fun args -> <@@ ((%%args[0]:obj) :?> Regex).Match(%%args[1]) :> obj @@>)

matchMeth.AddXmlDoc "Searches the specified input string for the first occurrence of this regular expression"

ty.AddMember matchMeth

Eftersom du skapar en instansmetod args[0] representerar den RegexTyped instans där metoden anropas och args[1] är indataargumentet.

Slutligen tillhandahåller du en konstruktor så att instanser av den angivna typen kan skapas.

let ctor =
    ProvidedConstructor(
        parameters = [],
        invokeCode = fun args -> <@@ Regex(pattern, options) :> obj @@>)

ctor.AddXmlDoc("Initializes a regular expression instance.")

ty.AddMember ctor

Konstruktorn raderar bara till skapandet av en standard .NET Regex-instans, som återigen boxas till ett objekt eftersom obj är radering av den angivna typen. Med den ändringen fungerar den API-exempelanvändning som angavs tidigare i ämnet som förväntat. Följande kod är klar och slutgiltig:

namespace Samples.FSharp.RegexTypeProvider

open System.Reflection
open Microsoft.FSharp.Core.CompilerServices
open Samples.FSharp.ProvidedTypes
open System.Text.RegularExpressions

[<TypeProvider>]
type public CheckedRegexProvider() as this =
    inherit TypeProviderForNamespaces()

    // Get the assembly and namespace used to house the provided types.
    let thisAssembly = Assembly.GetExecutingAssembly()
    let rootNamespace = "Samples.FSharp.RegexTypeProvider"
    let baseTy = typeof<obj>
    let staticParams = [ProvidedStaticParameter("pattern", typeof<string>)]

    let regexTy = ProvidedTypeDefinition(thisAssembly, rootNamespace, "RegexTyped", Some baseTy)

    do regexTy.DefineStaticParameters(
        parameters=staticParams,
        instantiationFunction=(fun typeName parameterValues ->

            match parameterValues with
            | [| :? string as pattern|] ->

                // Create an instance of the regular expression.

                let r = System.Text.RegularExpressions.Regex(pattern)

                // Declare the typed regex provided type.

                let ty =
                    ProvidedTypeDefinition(
                        thisAssembly,
                        rootNamespace,
                        typeName,
                        baseType = Some baseTy)

                ty.AddXmlDoc "A strongly typed interface to the regular expression '%s'"

                // Provide strongly typed version of Regex.IsMatch static method.
                let isMatch =
                    ProvidedMethod(
                        methodName = "IsMatch",
                        parameters = [ProvidedParameter("input", typeof<string>)],
                        returnType = typeof<bool>,
                        isStatic = true,
                        invokeCode = fun args -> <@@ Regex.IsMatch(%%args[0], pattern) @@>)

                isMatch.AddXmlDoc "Indicates whether the regular expression finds a match in the specified input string"

                ty.AddMember isMatch

                // Provided type for matches
                // Again, erase to obj even though the representation will always be a Match
                let matchTy =
                    ProvidedTypeDefinition(
                        "MatchType",
                        baseType = Some baseTy,
                        hideObjectMethods = true)

                // Nest the match type within parameterized Regex type.
                ty.AddMember matchTy

                // Add group properties to match type
                for group in r.GetGroupNames() do
                    // Ignore the group named 0, which represents all input.
                    if group <> "0" then
                        let prop =
                          ProvidedProperty(
                            propertyName = group,
                            propertyType = typeof<Group>,
                            getterCode = fun args -> <@@ ((%%args[0]:obj) :?> Match).Groups[group] @@>)
                        prop.AddXmlDoc(sprintf @"Gets the ""%s"" group from this match" group)
                        matchTy.AddMember(prop)

                // Provide strongly typed version of Regex.Match instance method.
                let matchMeth =
                  ProvidedMethod(
                    methodName = "Match",
                    parameters = [ProvidedParameter("input", typeof<string>)],
                    returnType = matchTy,
                    invokeCode = fun args -> <@@ ((%%args[0]:obj) :?> Regex).Match(%%args[1]) :> obj @@>)
                matchMeth.AddXmlDoc "Searches the specified input string for the first occurrence of this regular expression"

                ty.AddMember matchMeth

                // Declare a constructor.
                let ctor =
                  ProvidedConstructor(
                    parameters = [],
                    invokeCode = fun args -> <@@ Regex(pattern) :> obj @@>)

                // Add documentation to the constructor.
                ctor.AddXmlDoc "Initializes a regular expression instance"

                ty.AddMember ctor

                ty
            | _ -> failwith "unexpected parameter values"))

    do this.AddNamespace(rootNamespace, [regexTy])

[<TypeProviderAssembly>]
do ()

Viktiga lektioner

I det här avsnittet beskrivs hur du skapar en typprovider som fungerar på dess statiska parametrar. Providern kontrollerar den statiska parametern och tillhandahåller åtgärder baserat på dess värde.

En typprovider som backas upp av lokala data

Ofta kanske du vill att typprovidrar ska presentera API:er baserat på inte bara statiska parametrar utan även information från lokala eller fjärranslutna system. I det här avsnittet beskrivs typprovidrar som baseras på lokala data, till exempel lokala datafiler.

Enkel CSV-filprovider

Som ett enkelt exempel bör du överväga en typprovider för att komma åt vetenskapliga data i CSV-format (Kommaavgränsat värde). Det här avsnittet förutsätter att CSV-filerna innehåller en rubrikrad följt av flyttalsdata, vilket visas i följande tabell:

Avstånd (meter) Tid (sekund)
50,0 3.7
100,0 5.2
150.0 6.4

Det här avsnittet visar hur du anger en typ som du kan använda för att hämta rader med en Distance egenskap av typen float<meter> och en Time egenskap av typen float<second>. För enkelhetens skull görs följande antaganden:

  • Rubriknamn är antingen enhetslösa eller har formuläret "Namn (enhet)" och innehåller inte kommatecken.

  • Enheter är alla System International-enheter (SI) som modulen FSharp.Data.UnitSystems.SI.UnitNames Module (F#) definierar.

  • Enheter är alla enkla (till exempel mätare) snarare än sammansatta (till exempel meter/sekund).

  • Alla kolumner innehåller flyttalsdata.

En mer komplett leverantör skulle lätta på dessa begränsningar.

Återigen är det första steget att överväga hur API:et ska se ut. Med tanke på en info.csv fil med innehållet från den föregående tabellen (i kommaavgränsat format) bör provideranvändare kunna skriva kod som liknar följande exempel:

let info = new MiniCsv<"info.csv">()
for row in info.Data do
let time = row.Time
printfn $"{float time}"

I det här fallet bör kompilatorn konvertera dessa anrop till något som liknar följande exempel:

let info = new CsvFile("info.csv")
for row in info.Data do
let (time:float) = row[1]
printfn $"%f{float time}"

Den optimala översättningen kräver att typprovidern definierar en verklig CsvFile typ i typproviderns sammansättning. Typprovidrar förlitar sig ofta på några hjälptyper och metoder för att omsluta viktig logik. Eftersom mått raderas vid körning kan du använda en float[] som raderingstyp för en rad. Kompilatorn behandlar olika kolumner med olika måtttyper. Den första kolumnen i vårt exempel har till exempel typen float<meter>, och den andra har float<second>. Den borttagna representationen kan dock förbli ganska enkel.

Följande kod visar kärnan i implementeringen.

// Simple type wrapping CSV data
type CsvFile(filename) =
    // Cache the sequence of all data lines (all lines but the first)
    let data =
        seq {
            for line in File.ReadAllLines(filename) |> Seq.skip 1 ->
                line.Split(',') |> Array.map float
        }
        |> Seq.cache
    member _.Data = data

[<TypeProvider>]
type public MiniCsvProvider(cfg:TypeProviderConfig) as this =
    inherit TypeProviderForNamespaces(cfg)

    // Get the assembly and namespace used to house the provided types.
    let asm = System.Reflection.Assembly.GetExecutingAssembly()
    let ns = "Samples.FSharp.MiniCsvProvider"

    // Create the main provided type.
    let csvTy = ProvidedTypeDefinition(asm, ns, "MiniCsv", Some(typeof<obj>))

    // Parameterize the type by the file to use as a template.
    let filename = ProvidedStaticParameter("filename", typeof<string>)
    do csvTy.DefineStaticParameters([filename], fun tyName [| :? string as filename |] ->

        // Resolve the filename relative to the resolution folder.
        let resolvedFilename = Path.Combine(cfg.ResolutionFolder, filename)

        // Get the first line from the file.
        let headerLine = File.ReadLines(resolvedFilename) |> Seq.head

        // Define a provided type for each row, erasing to a float[].
        let rowTy = ProvidedTypeDefinition("Row", Some(typeof<float[]>))

        // Extract header names from the file, splitting on commas.
        // use Regex matching to get the position in the row at which the field occurs
        let headers = Regex.Matches(headerLine, "[^,]+")

        // Add one property per CSV field.
        for i in 0 .. headers.Count - 1 do
            let headerText = headers[i].Value

            // Try to decompose this header into a name and unit.
            let fieldName, fieldTy =
                let m = Regex.Match(headerText, @"(?<field>.+) \((?<unit>.+)\)")
                if m.Success then

                    let unitName = m.Groups["unit"].Value
                    let units = ProvidedMeasureBuilder.Default.SI unitName
                    m.Groups["field"].Value, ProvidedMeasureBuilder.Default.AnnotateType(typeof<float>,[units])

                else
                    // no units, just treat it as a normal float
                    headerText, typeof<float>

            let prop =
                ProvidedProperty(fieldName, fieldTy,
                    getterCode = fun [row] -> <@@ (%%row:float[])[i] @@>)

            // Add metadata that defines the property's location in the referenced file.
            prop.AddDefinitionLocation(1, headers[i].Index + 1, filename)
            rowTy.AddMember(prop)

        // Define the provided type, erasing to CsvFile.
        let ty = ProvidedTypeDefinition(asm, ns, tyName, Some(typeof<CsvFile>))

        // Add a parameterless constructor that loads the file that was used to define the schema.
        let ctor0 =
            ProvidedConstructor([],
                invokeCode = fun [] -> <@@ CsvFile(resolvedFilename) @@>)
        ty.AddMember ctor0

        // Add a constructor that takes the file name to load.
        let ctor1 = ProvidedConstructor([ProvidedParameter("filename", typeof<string>)],
            invokeCode = fun [filename] -> <@@ CsvFile(%%filename) @@>)
        ty.AddMember ctor1

        // Add a more strongly typed Data property, which uses the existing property at run time.
        let prop =
            ProvidedProperty("Data", typedefof<seq<_>>.MakeGenericType(rowTy),
                getterCode = fun [csvFile] -> <@@ (%%csvFile:CsvFile).Data @@>)
        ty.AddMember prop

        // Add the row type as a nested type.
        ty.AddMember rowTy
        ty)

    // Add the type to the namespace.
    do this.AddNamespace(ns, [csvTy])

Observera följande punkter om implementeringen:

  • Överlagrade konstruktorer tillåter att antingen den ursprungliga filen eller en som har ett identiskt schema kan läsas. Det här mönstret är vanligt när du skriver en typprovider för lokala eller fjärranslutna datakällor, och det här mönstret gör att en lokal fil kan användas som mall för fjärrdata.

  • Du kan använda värdet TypeProviderConfig som skickas till typproviderkonstruktorn för att matcha relativa filnamn.

  • Du kan använda AddDefinitionLocation metoden för att definiera platsen för de angivna egenskaperna. Om du använder Go To Definition på en angivet egenskap öppnas DÄRFÖR CSV-filen i Visual Studio.

  • Du kan använda typen ProvidedMeasureBuilder för att slå upp SI-enheterna och generera relevanta float<_> typer.

Viktiga lektioner

I det här avsnittet beskrivs hur du skapar en typprovider för en lokal datakälla med ett enkelt schema som finns i själva datakällan.

Gå vidare

Följande avsnitt innehåller förslag på ytterligare studier.

En titt på den kompilerade koden för borttagna typer

Om du vill ge dig en uppfattning om hur användningen av typprovidern motsvarar koden som genereras kan du titta på följande funktion med hjälp HelloWorldTypeProvider av den som användes tidigare i det här avsnittet.

let function1 () =
    let obj1 = Samples.HelloWorldTypeProvider.Type1("some data")
    obj1.InstanceProperty

Här är en bild av den resulterande koden som dekompileras med hjälp av ildasm.exe:

.class public abstract auto ansi sealed Module1
extends [mscorlib]System.Object
{
.custom instance void [FSharp.Core]Microsoft.FSharp.Core.CompilationMappingAtt
ribute::.ctor(valuetype [FSharp.Core]Microsoft.FSharp.Core.SourceConstructFlags)
= ( 01 00 07 00 00 00 00 00 )
.method public static int32  function1() cil managed
{
// Code size       24 (0x18)
.maxstack  3
.locals init ([0] object obj1)
IL_0000:  nop
IL_0001:  ldstr      "some data"
IL_0006:  unbox.any  [mscorlib]System.Object
IL_000b:  stloc.0
IL_000c:  ldloc.0
IL_000d:  call       !!0 [FSharp.Core_2]Microsoft.FSharp.Core.LanguagePrimit
ives/IntrinsicFunctions::UnboxGeneric<string>(object)
IL_0012:  callvirt   instance int32 [mscorlib_3]System.String::get_Length()
IL_0017:  ret
} // end of method Module1::function1

} // end of class Module1

Som exemplet visar har alla omnämnanden av typen Type1 och InstanceProperty egenskapen raderats, vilket endast lämnar åtgärder på de körningstyper som ingår.

Design- och namngivningskonventioner för typprovidrar

Observera följande konventioner när du redigerar typprovidrar.

Providers för Anslut ivity Protocols I allmänhet bör namnen på de flesta provider-DLL:er för data- och tjänstanslutningsprotokoll, till exempel OData- eller SQL-anslutningar, sluta i TypeProvider eller TypeProviders. Använd till exempel ett DLL-namn som liknar följande sträng:

Fabrikam.Management.BasicTypeProviders.dll

Se till att dina angivna typer är medlemmar i motsvarande namnområde och ange anslutningsprotokollet som du implementerade:

  Fabrikam.Management.BasicTypeProviders.WmiConnection<…>
  Fabrikam.Management.BasicTypeProviders.DataProtocolConnection<…>

Verktygsleverantörer för allmän kodning. För en leverantör av verktygstyp som den för reguljära uttryck kan typprovidern ingå i ett basbibliotek, som följande exempel visar:

#r "Fabrikam.Core.Text.Utilities.dll"

I det här fallet visas den angivna typen vid en lämplig tidpunkt enligt normala .NET-designkonventioner:

  open Fabrikam.Core.Text.RegexTyped

  let regex = new RegexTyped<"a+b+a+b+">()

Singleton-datakällor. Vissa typprovidrar ansluter till en enda dedikerad datakälla och tillhandahåller endast data. I det här fallet bör du släppa suffixet TypeProvider och använda vanliga konventioner för .NET-namngivning:

#r "Fabrikam.Data.Freebase.dll"

let data = Fabrikam.Data.Freebase.Astronomy.Asteroids

Mer information finns i GetConnection designkonventionen som beskrivs senare i det här avsnittet.

Designmönster för typprovidrar

I följande avsnitt beskrivs designmönster som du kan använda när du skapar typprovidrar.

Designmönster för Get Anslut ion

De flesta typprovidrar bör skrivas för att använda det GetConnection mönster som används av typprovidrar i FSharp.Data.TypeProviders.dll, som följande exempel visar:

#r "Fabrikam.Data.WebDataStore.dll"

type Service = Fabrikam.Data.WebDataStore<…static connection parameters…>

let connection = Service.GetConnection(…dynamic connection parameters…)

let data = connection.Astronomy.Asteroids

Typprovidrar som backas upp av fjärrdata och tjänster

Innan du skapar en typprovider som backas upp av fjärrdata och tjänster måste du överväga en rad problem som ingår i ansluten programmering. Dessa problem omfattar följande överväganden:

  • schemamappning

  • livskraft och ogiltighet i närvaro av schemaändring

  • schemacachelagring

  • asynkrona implementeringar av dataåtkomståtgärder

  • stödfrågor, inklusive LINQ-frågor

  • autentiseringsuppgifter och autentisering

Det här avsnittet utforskar inte dessa problem ytterligare.

Ytterligare redigeringstekniker

När du skriver egna typprovidrar kanske du vill använda följande ytterligare tekniker.

Skapa typer och medlemmar på begäran

ProvidedType-API:et har fördröjda versioner av AddMember.

  type ProvidedType =
      member AddMemberDelayed  : (unit -> MemberInfo)      -> unit
      member AddMembersDelayed : (unit -> MemberInfo list) -> unit

Dessa versioner används för att skapa blanksteg på begäran av typer.

Tillhandahålla matristyper och instansiering av allmän typ

Du gör tillhandahållna medlemmar (vars signaturer inkluderar matristyper, byref-typer och instansiering av generiska typer) med hjälp av den normala MakeArrayType, MakePointerTypeoch MakeGenericType på alla instanser av Type, inklusive ProvidedTypeDefinitions.

Kommentar

I vissa fall kan du behöva använda hjälpen i ProvidedTypeBuilder.MakeGenericType. Mer information finns i SDK-dokumentationen för typprovider.

Ange måttenhetsanteckningar

ProvidedTypes-API:et innehåller hjälpfunktioner för att tillhandahålla måttanteckningar. Om du till exempel vill ange typen float<kg>använder du följande kod:

  let measures = ProvidedMeasureBuilder.Default
  let kg = measures.SI "kilogram"
  let m = measures.SI "meter"
  let float_kg = measures.AnnotateType(typeof<float>,[kg])

Om du vill ange typen Nullable<decimal<kg/m^2>>använder du följande kod:

  let kgpm2 = measures.Ratio(kg, measures.Square m)
  let dkgpm2 = measures.AnnotateType(typeof<decimal>,[kgpm2])
  let nullableDecimal_kgpm2 = typedefof<System.Nullable<_>>.MakeGenericType [|dkgpm2 |]

Åtkomst till projektlokala eller skriptlokala resurser

Varje instans av en typprovider kan ges ett TypeProviderConfig värde under konstruktionen. Det här värdet innehåller "lösningsmappen" för providern (dvs. projektmappen för kompilering eller katalogen som innehåller ett skript), listan över refererade sammansättningar och annan information.

Ogiltigförklarande

Leverantörer kan generera ogiltighetssignaler för att meddela F#-språktjänsten att schemaantagandena kan ha ändrats. När ogiltighet inträffar görs en typecheck om providern finns i Visual Studio. Den här signalen ignoreras när providern finns i F# Interactive eller av F#-kompilatorn (fsc.exe).

Cachelagring schemainformation

Leverantörer måste ofta cachelagrat åtkomst till schemainformation. Cachelagrade data ska lagras med hjälp av ett filnamn som anges som en statisk parameter eller som användardata. Ett exempel på schemacachelagring är parametern LocalSchemaFile i typprovidrar i FSharp.Data.TypeProviders sammansättningen. I implementeringen av dessa leverantörer instruerar den här statiska parametern typprovidern att använda schemainformationen i den angivna lokala filen i stället för att komma åt datakällan via nätverket. Om du vill använda cachelagrad schemainformation måste du också ange den statiska parametern ForceUpdate till false. Du kan använda en liknande teknik för att aktivera dataåtkomst online och offline.

Säkerhetskopiera sammansättning

När du kompilerar en .dll eller .exe fil länkas säkerhetskopieringen .dll fil för genererade typer statiskt till den resulterande sammansättningen. Den här länken skapas genom att kopiera definitionerna av typen Mellanliggande språk (IL) och alla hanterade resurser från säkerhetskopieringssammansättningen till den slutliga sammansättningen. När du använder F# Interactive kopieras inte .dll fil och läses i stället in direkt i den interaktiva F#-processen.

Undantag och diagnostik från typprovidrar

Alla användningar av alla medlemmar från angivna typer kan utlösa undantag. Om en typprovider i samtliga fall utlöser ett undantag, tillskriver värdkompilatorn felet till en viss typprovider.

  • Typproviderns undantag får aldrig resultera i interna kompilatorfel.

  • Typprovidrar kan inte rapportera varningar.

  • När en typprovider finns i F#-kompilatorn, en F#-utvecklingsmiljö eller F# Interactive fångas alla undantag från providern. Egenskapen Meddelande är alltid feltexten och ingen stackspårning visas. Om du ska utlösa ett undantag kan du skapa följande exempel: System.NotSupportedException, System.IO.IOException, System.Exception.

Tillhandahålla genererade typer

Hittills har det här dokumentet förklarat hur du tillhandahåller raderade typer. Du kan också använda typprovidermekanismen i F# för att tillhandahålla genererade typer, som läggs till som verkliga .NET-typdefinitioner i användarnas program. Du måste referera till genererade angivna typer med hjälp av en typdefinition.

open Microsoft.FSharp.TypeProviders

type Service = ODataService<"http://services.odata.org/Northwind/Northwind.svc/">

Hjälpkoden ProvidedTypes-0.2 som ingår i F# 3.0-versionen har endast begränsat stöd för att tillhandahålla genererade typer. Följande instruktioner måste vara sanna för en genererad typdefinition:

  • isErased måste anges till false.

  • Den genererade typen måste läggas till i en nykonstruerad ProvidedAssembly(), som representerar en container för genererade kodfragment.

  • Providern måste ha en sammansättning som har en faktisk .NET-.dll fil med en matchande .dll fil på disken.

Regler och begränsningar

Tänk på följande regler och begränsningar när du skriver typprovidrar.

Angivna typer måste kunna nås

Alla angivna typer ska kunna nås från de icke-kapslade typerna. De icke-kapslade typerna anges i anropet TypeProviderForNamespaces till konstruktorn eller ett anrop till AddNamespace. Om providern till exempel tillhandahåller en typ StaticClass.P : Tmåste du se till att T antingen är en icke-kapslad typ eller kapslad under en.

Vissa leverantörer har till exempel en statisk klass som DataTypes innehåller dessa T1, T2, T3, ... typer. Annars står det i felet att en referens till typ T i sammansättning A hittades, men att typen inte kunde hittas i den sammansättningen. Om det här felet visas kontrollerar du att alla dina undertyper kan nås från providertyperna. Obs! Dessa T1, T2, T3... typer kallas för "on-the-fly"-typerna. Kom ihåg att placera dem i ett tillgängligt namnområde eller en överordnad typ.

Begränsningar för typprovidermekanismen

Typprovidermekanismen i F# har följande begränsningar:

  • Den underliggande infrastrukturen för typprovidrar i F# stöder inte angivna generiska typer eller tillhandahållna generiska metoder.

  • Mekanismen stöder inte kapslade typer med statiska parametrar.

Utvecklingstips

Följande tips kan vara användbara under utvecklingsprocessen:

Köra två instanser av Visual Studio

Du kan utveckla typprovidern i en instans och testa providern i den andra eftersom test-IDE:t låser .dll-filen som förhindrar att typprovidern återskapas. Därför måste du stänga den andra instansen av Visual Studio medan providern är inbyggd i den första instansen och sedan måste du öppna den andra instansen igen när providern har skapats.

Felsöka typprovidrar med hjälp av anrop av fsc.exe

Du kan anropa typprovidrar med hjälp av följande verktyg:

  • fsc.exe (F#-kommandoradskompilatorn)

  • fsi.exe (Den interaktiva F#-kompilatorn)

  • devenv.exe (Visual Studio)

Du kan ofta felsöka typprovidrar enklast genom att använda fsc.exe på en testskriptfil (till exempel script.fsx). Du kan starta ett felsökningsprogram från en kommandotolk.

devenv /debugexe fsc.exe script.fsx

Du kan använda utskrifts-till-stdout-loggning.

Se även