Share via


Oktatóanyag: Típusszolgáltató létrehozása

Az F# típusszolgáltatói mechanizmusa jelentős részét képezi az információgazdag programozás támogatásának. Ez az oktatóanyag bemutatja, hogyan hozhat létre saját típusszolgáltatókat azáltal, hogy végigvezeti önt számos egyszerű típusszolgáltató fejlesztésén az alapfogalmak szemléltetéséhez. Az F# típusszolgáltatói mechanizmusával kapcsolatos további információkért lásd: Típusszolgáltatók.

Az F#-ökoszisztéma számos típusszolgáltatót tartalmaz a gyakran használt internetes és vállalati adatszolgáltatásokhoz. Példa:

  • Az FSharp.Data JSON-, XML-, CSV- és HTML-dokumentumformátumokhoz tartalmaz típusszolgáltatókat.

  • A SwaggerProvider két olyan generatív típusú szolgáltatót tartalmaz, amelyek objektummodellt és HTTP-ügyfeleket hoznak létre az OpenApi 3.0 és a Swagger 2.0 sémák által leírt API-khoz.

  • Az FSharp.Data.SqlClient típusszolgáltatók készletével rendelkezik a T-SQL lefordítási idő ellenőrzött beágyazásához az F#-ban.

Létrehozhat egyéni típusszolgáltatókat, vagy hivatkozhat a mások által létrehozott típusszolgáltatókra. A szervezet például rendelkezhet olyan adatszolgáltatással, amely nagy és növekvő számú elnevezett adathalmazt biztosít, amelyek mindegyike saját stabil adatsémával rendelkezik. Létrehozhat egy típusszolgáltatót, amely beolvassa a sémákat, és erősen gépelt módon jeleníti meg az aktuális adatkészleteket a programozónak.

Kezdés előtt

A típusszolgáltatói mechanizmus elsősorban stabil adat- és szolgáltatásinformációs terek F# programozási felületbe való injektálására szolgál.

Ez a mechanizmus nem olyan információs terek injektálására szolgál, amelyek sémaváltozásai a program végrehajtása során a program logikája szempontjából releváns módon változnak. A mechanizmust nem nyelvközi metaprogramozásra tervezték, annak ellenére, hogy ez a tartomány érvényes felhasználási módokat tartalmaz. Ezt a mechanizmust csak akkor érdemes használni, ha szükséges, és ahol egy típusszolgáltató fejlesztése nagyon magas értéket eredményez.

Ne írjon olyan típusszolgáltatót, amelynél nem érhető el séma. Hasonlóképpen kerülnie kell egy olyan típusszolgáltató írását is, amelyben egy szokásos (vagy akár egy meglévő) .NET-kódtár elegendő lenne.

Mielőtt hozzákezd, a következő kérdéseket teheti fel:

  • Van sémája az adatforráshoz? Ha igen, mi a megfeleltetés az F# és a .NET típusú rendszerbe?

  • Használhat egy meglévő (dinamikusan beírt) API-t a megvalósítás kiindulópontjaként?

  • Önnek és a szervezetének van-e elegendő felhasználási módja a típusszolgáltatónak ahhoz, hogy érdemes legyen írni? Egy normál .NET-kódtár megfelel az igényeinek?

  • Mennyit változik a séma?

  • Változik a kódolás során?

  • Változik a kódolási munkamenetek között?

  • Megváltozik a program végrehajtása során?

A típusszolgáltatók olyan helyzetekhez ideálisak, amikor a séma futásidőben és a lefordított kód élettartama alatt is stabil.

Egyszerű típusszolgáltató

Ez a minta a Samples.HelloWorldTypeProvider, az F# Type Provider SDK könyvtárában található examples mintákhoz hasonlóan. A szolgáltató egy 100 törölt típust tartalmazó "típusteret" tesz elérhetővé, ahogyan az alábbi kód is mutatja az F# aláírás szintaxisával, és kihagyja a részleteket az összes kivételével Type1. A törölt típusokkal kapcsolatos további információkért lásd a jelen témakör későbbi, törölt adattípusairól szóló szakaszt.

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 =
…

Vegye figyelembe, hogy a megadott típusok és tagok halmaza statikusan ismert. Ez a példa nem használja ki a szolgáltatók azon képességét, hogy sémától függő típusokat adjanak meg. A típusszolgáltató implementációját az alábbi kód ismerteti, és a részleteket a témakör későbbi szakaszai ismertetik.

Figyelmeztetés

A kód és az online minták között eltérések lehetnek.

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()

A szolgáltató használatához nyissa meg a Visual Studio egy külön példányát, hozzon létre egy F#-szkriptet, majd adjon hozzá egy hivatkozást a szolgáltatóhoz a szkriptből a #r használatával, ahogyan az alábbi kód mutatja:

#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

Ezután keresse meg a típusokat a Samples.HelloWorldTypeProvider típusszolgáltató által létrehozott névtérben.

A szolgáltató újrafordítása előtt győződjön meg arról, hogy bezárta a Visual Studio és az F# Interactive összes olyan példányát, amely a szolgáltató DLL-t használja. Ellenkező esetben buildelési hiba történik, mert a kimeneti DLL zárolva lesz.

A szolgáltató nyomtatási utasítások használatával történő hibakereséséhez hozzon létre egy szkriptet, amely a szolgáltatóval kapcsolatos problémát tesz közzé, majd használja a következő kódot:

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

A szolgáltató Visual Studio használatával történő hibakereséséhez nyissa meg a Visual Studio fejlesztői parancssorát rendszergazdai hitelesítő adatokkal, és futtassa a következő parancsot:

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

Másik lehetőségként nyissa meg a Visual Studiót, nyissa meg a Hibakeresés menüt, válassza ki Debug/Attach to process…és csatolja egy másik devenv folyamathoz, ahol a szkriptet szerkeszti. Ezzel a módszerrel könnyebben megcélzhatja a típusszolgáltató adott logikáját úgy, hogy interaktívan beírja a kifejezéseket a második példányba (teljes IntelliSense és egyéb funkciókkal).

Letilthatja a Just My Code hibakeresést a létrehozott kód hibáinak jobb azonosítása érdekében. A funkció engedélyezésével vagy letiltásával kapcsolatos információkért lásd : Navigálás a kódon a hibakeresővel. Emellett a menü megnyitásával, majd Exceptions a Debug Ctrl+Alt+E billentyűkombinációval a párbeszédpanel megnyitásához is beállíthatja az Exceptions első esélyű kivételt. A párbeszédpanel alatt Common Language Runtime Exceptionsjelölje be a Thrown jelölőnégyzetet.

A típusszolgáltató megvalósítása

Ez a szakasz végigvezeti a típusszolgáltató implementációjának fő szakaszain. Először meg kell határoznia magának az egyéni típusszolgáltatónak a típusát:

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

Ennek a típusnak nyilvánosnak kell lennie, és meg kell jelölnie a TypeProvider attribútummal, hogy a fordító felismerje a típusszolgáltatót, amikor egy külön F#-projekt hivatkozik a típust tartalmazó szerelvényre. A konfigurációs paraméter nem kötelező, és ha van ilyen, az F#-fordító által létrehozott típusszolgáltatói példány környezeti konfigurációs adatait tartalmazza.

Ezután implementálja az ITypeProvider felületet. Ebben az esetben az TypeProviderForNamespaces API-ból származó ProvidedTypes típust használja alaptípusként. Ez a segédtípus véges gyűjteményt biztosíthat a lelkesen megadott névterekből, amelyek mindegyike közvetlenül tartalmaz véges számú rögzített, lelkesen megadott típust. Ebben az összefüggésben a szolgáltató lelkesen hoz létre típusokat, még akkor is, ha nincs rájuk szükség vagy használják őket.

inherit TypeProviderForNamespaces(config)

Ezután adja meg a megadott típusok névterét meghatározó helyi privát értékeket, és keresse meg magát a típusszolgáltatói szerelvényt. A rendszer ezt a szerelvényt használja később a megadott törölt típusok logikai szülőtípusaként.

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

Ezután hozzon létre egy függvényt, amely az 1. típustípust adja meg... Típus100. Ezt a függvényt a jelen témakör későbbi részében részletesebben ismertetik.

let makeOneProvidedType (n:int) = …

Ezután hozza létre a 100 megadott típust:

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

Ezután adja hozzá a típusokat megadott névtérként:

do this.AddNamespace(namespaceName, types)

Végül adjon hozzá egy szerelvényattribútumot, amely azt jelzi, hogy típusszolgáltatói DLL-t hoz létre:

[<assembly:TypeProviderAssembly>]
do()

Egy típus és tagjai megadása

A makeOneProvidedType függvény elvégzi az egyik típus megadásának valódi munkáját.

let makeOneProvidedType (n:int) =
…

Ez a lépés a függvény implementálását ismerteti. Először hozza létre a megadott típust (például 1. típus, amikor n = 1 vagy Type57, amikor 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>)

Jegyezze fel a következő pontokat:

  • Ez a megadott típus törlődik. Mivel az alaptípust objjelzi, a példányok obj típusú értékekként jelennek meg a lefordított kódban.

  • Ha nem beágyazott típust ad meg, meg kell adnia a szerelvényt és a névteret. Törölt típusok esetén a szerelvénynek magának a típusszolgáltatói szerelvénynek kell lennie.

Ezután adja hozzá az XML-dokumentációt a típushoz. Ez a dokumentáció késik, azaz igény szerint lesz kiszámítva, ha a gazdagépfordítónak szüksége van rá.

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

Ezután hozzáad egy megadott statikus tulajdonságot a típushoz:

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

A tulajdonság lekérése mindig a "Hello!" sztringre lesz kiértékelve. A GetterCode tulajdonsághoz egy F# idézőjelet használ, amely a gazdagépfordító által a tulajdonság lekéréséhez létrehozott kódot jelöli. Az idézőjelekről további információt a Kódajánlatok (F#) című témakörben talál.

XML-dokumentáció hozzáadása a tulajdonsághoz.

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

Ezután csatolja a megadott tulajdonságot a megadott típushoz. A megadott tagot csak egy típushoz kell csatolnia. Ellenkező esetben a tag soha nem lesz elérhető.

t.AddMember staticProp

Most hozzon létre egy megadott konstruktort, amely nem vesz fel paramétereket.

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

A InvokeCode konstruktorhoz egy F# idézőjelet ad vissza, amely a gazdafordító által a konstruktor meghívásakor generált kódot jelöli. Használhatja például a következő konstruktort:

new Type10()

A megadott típus egy példánya "Az objektumadatok" mögöttes adatokkal jön létre. Az idézett kód tartalmaz egy obj-ra való átalakítást, mert ez a típus a megadott típus törlése (a megadott típus deklarálásakor megadott módon).

Xml-dokumentáció hozzáadása a konstruktorhoz, és adja hozzá a megadott konstruktort a megadott típushoz:

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

t.AddMember ctor

Hozzon létre egy második konstruktort, amely egy paramétert vesz igénybe:

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

A InvokeCode konstruktor esetében ismét egy F# idézőjelet ad vissza, amely azt a kódot jelöli, amelyet a gazdagépfordító generált a metódus hívásához. Használhatja például a következő konstruktort:

new Type10("ten")

A megadott típus egy példánya a "ten" mögöttes adatokkal jön létre. Lehet, hogy már észrevette, hogy a InvokeCode függvény idézőjelet ad vissza. A függvény bemenete a kifejezések listája, konstruktorparaméterenként egy. Ebben az esetben egy olyan kifejezés érhető el, amely az egyetlen paraméter értékét jelöli.args[0] A konstruktor felé irányuló hívás kódja a visszaadott értéket a törölt típusra objkényszeríti. Miután hozzáadta a második konstruktort a típushoz, létre kell hoznia egy megadott példánytulajdonságot:

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

A tulajdonság lekérése a sztring hosszát adja vissza, amely a reprezentációs objektum. A GetterCode tulajdonság egy F# idézőjelet ad vissza, amely megadja a gazdagépfordító által a tulajdonság lekéréséhez létrehozott kódot. GetterCode A függvény például InvokeCodeidézőjelet ad vissza. A gazdagépfordító argumentumlistával hívja meg ezt a függvényt. Ebben az esetben az argumentumok csak azt az egyetlen kifejezést tartalmazzák, amely azt a példányt jelöli, amelyre a getter meghívása történik, amelyet a használatával args[0]érhet el. Az eredmény idézőjelbe való implementációja GetterCode a törölt típusnál objtörténik, és a fordító mechanizmusának megfelelő öntéssel ellenőrzi, hogy az objektum sztring-e. A következő rész makeOneProvidedType egy példánymetódushoz biztosít egy paramétert.

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

Végül hozzon létre egy beágyazott típust, amely 100 beágyazott tulajdonságot tartalmaz. Ennek a beágyazott típusnak és tulajdonságainak létrehozása késik, azaz igény szerint lesz kiszámítva.

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])

A törölt megadott típusok részletei

Az ebben a szakaszban szereplő példa csak törölt típusokat tartalmaz, amelyek különösen hasznosak a következő helyzetekben:

  • Amikor szolgáltatót ír egy olyan információs területhez, amely csak adatokat és metódusokat tartalmaz.

  • Amikor olyan szolgáltatót ír, ahol a pontos futásidejű szemantikák nem kritikus fontosságúak az információs terület gyakorlati használatához.

  • Amikor olyan nagy és összekapcsolt információs tér szolgáltatót ír, hogy technikailag nem megvalósítható valós .NET-típusok létrehozása az információs területhez.

Ebben a példában a rendszer minden megadott típust töröl a beíráshoz obj, és a típus összes használata típusként obj jelenik meg a lefordított kódban. A példák alapjául szolgáló objektumok valójában sztringek, de a típus a .NET által lefordított kódban System.Object fog megjelenni. Ahogy a típustörlés minden felhasználási módja esetében, a törölt típusok felforgatásához explicit boxolást, törlést és öntést is használhat. Ebben az esetben a nem érvényes leadott kivétel az objektum használata esetén eredményezheti az eredményt. A szolgáltatói futtatókörnyezetek saját magánreprezentációs típust határozhatnak meg a hamis reprezentációk elleni védelem érdekében. A törölt típusok nem határozhatók meg az F#-ban. Csak a megadott típusok törölhetők. Ismernie kell a gyakorlati és szemantikai következményeket is, ha a típusszolgáltató vagy a törölt típusok valamelyikét tartalmazó szolgáltatót használ. A törölt típus nem rendelkezik valódi .NET-típussal. Ezért nem tud pontosan tükrözni a típust, és a törölt típusok felforgathatók, ha futásidejű vetítéseket és más technikákat használ, amelyek pontos futtatókörnyezettípus-szemantikára támaszkodnak. A törölt típusok visszaforgatása gyakran eredményez típuselkülönítési kivételeket a futtatáskor.

A törölt megadott típusok reprezentációinak kiválasztása

A törölt megadott típusok egyes felhasználási módjaihoz nincs szükség ábrázolásra. A törölt megadott típus például csak statikus tulajdonságokat és tagokat és konstruktorokat tartalmazhat, és egyetlen metódus vagy tulajdonság sem ad vissza ilyen típusú példányt. Ha el tud érni egy törölt típus példányait, vegye figyelembe a következő kérdéseket:

Mi a megadott típusú törlés?

  • A megadott típus törlése az, ahogyan a típus megjelenik a lefordított .NET-kódban.

  • A megadott törölt osztálytípus törlése mindig az első nem törölt alaptípus a típus öröklési láncában.

  • A megadott törölt felülettípus törlése mindig System.Objectaz .

Mik a megadott típus ábrázolásai?

  • A törölt típus lehetséges objektumainak halmazát annak ábrázolásának nevezzük. Ebben a dokumentumban a példában az összes törölt adattípus Type1..Type100 ábrázolása mindig sztringobjektum.

A megadott típus minden ábrázolásának kompatibilisnek kell lennie a megadott típus törlésével. (Ellenkező esetben az F#-fordító hibát ad a típusszolgáltató használatához, vagy a nem érvényes ellenőrizhető .NET-kód jön létre. A típusszolgáltató nem érvényes, ha olyan kódot ad vissza, amely érvénytelen megjelenítést ad vissza.)

A megadott objektumok ábrázolását az alábbi módszerek egyikével választhatja ki, amelyek mindegyike nagyon gyakori:

  • Ha egy meglévő .NET-típushoz egyszerűen egy erősen gépelt burkolót ad meg, akkor gyakran van értelme a típusnak arra a típusra törölnie, az ilyen típusú példányokat reprezentációként vagy mindkettőként használnia. Ez a megközelítés akkor megfelelő, ha az adott típus legtöbb meglévő metódusa még mindig értelmes az erősen gépelt verzió használatakor.

  • Ha olyan API-t szeretne létrehozni, amely jelentősen eltér a meglévő .NET API-któl, érdemes olyan futtatókörnyezet-típusokat létrehozni, amelyek a megadott típusok típustörlése és megjelenítései lesznek.

A dokumentumban szereplő példa sztringeket használ a megadott objektumok ábrázolásaként. Gyakran előfordulhat, hogy más objektumokat is érdemes használni a reprezentációkhoz. Használhat például egy szótárt tulajdonságcsomagként:

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

Másik lehetőségként megadhat egy típust a típusszolgáltatóban, amelyet futásidőben fog használni a reprezentáció létrehozásához egy vagy több futtatókörnyezeti művelettel együtt:

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

A megadott tagok ezután ilyen típusú példányokat hozhatnak létre:

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

Ebben az esetben (opcionálisan) használhatja ezt a típust a típus törléséhez, ha a következő típust adja meg a baseTypeProvidedTypeDefinitionkövetkező létrehozásakor:

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

Főbb tanulságok

Az előző szakasz bemutatta, hogyan hozhat létre egyszerű törlési típusú szolgáltatót, amely számos típust, tulajdonságot és metódust biztosít. Ez a szakasz a típustörlés fogalmát is ismertette, beleértve a típusszolgáltatótól származó törölt típusok biztosításának néhány előnyét és hátrányait, valamint a törölt típusok ábrázolását.

Statikus paramétereket használó típusszolgáltató

A típusszolgáltatók statikus adatokkal történő paraméterezése számos érdekes forgatókönyvet tesz lehetővé, még akkor is, ha a szolgáltatónak nem kell hozzáférnie helyi vagy távoli adatokhoz. Ebben a szakaszban megismerhet néhány alapvető technikát egy ilyen szolgáltató összeállításához.

Írja be a bejelölt Regex-szolgáltatót

Tegyük fel, hogy olyan típusszolgáltatót szeretne implementálni a normál kifejezésekhez, amelyek a .NET-kódtárakat Regex egy olyan felületen burkolják, amely a következő fordítási időt garantálja:

  • Annak ellenőrzése, hogy egy reguláris kifejezés érvényes-e.

  • Névvel ellátott tulajdonságok megadása olyan egyezéseken, amelyek a normál kifejezésben szereplő csoportneveken alapulnak.

Ez a szakasz bemutatja, hogyan hozhat létre típusszolgáltatókat egy RegexTyped olyan típus létrehozásához, amelyet a reguláris kifejezésminta paraméterez az előnyök biztosítása érdekében. A fordító hibát fog jelenteni, ha a megadott minta érvénytelen, és a típusszolgáltató kinyerheti a csoportokat a mintából, hogy az egyezések nevesített tulajdonságaival elérhesse őket. A típusszolgáltató tervezésekor figyelembe kell vennie, hogy a közzétett API-nak hogyan kell kinéznie a végfelhasználók számára, és hogy ez a kialakítás hogyan fog .NET-kódra lefordítani. Az alábbi példa bemutatja, hogyan használhat ilyen API-t a körzetszám összetevőinek lekéréséhez:

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"

Az alábbi példa bemutatja, hogy a típusszolgáltató hogyan fordítja le ezeket a hívásokat:

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"

Vegye figyelembe az alábbiakat:

  • A standard Regex típus a paraméteres típust RegexTyped jelöli.

  • A RegexTyped konstruktor a Regex konstruktor hívását eredményezi, amely a minta statikus típusargumentumát adja át.

  • A metódus eredményeit a Match standard Match típus jelöli.

  • Minden elnevezett csoport egy megadott tulajdonságot eredményez, és a tulajdonság elérése egy indexelő használatát eredményezi egy egyezés gyűjteményében Groups .

A következő kód az ilyen szolgáltató implementálásának logikája, és ez a példa kihagyja az összes tag hozzáadását a megadott típushoz. Az egyes hozzáadott tagokkal kapcsolatos információkért tekintse meg a témakör későbbi, megfelelő szakaszát.

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 ()

Vegye figyelembe az alábbiakat:

  • A típusszolgáltató két statikus paramétert vesz fel: a patternkötelezőt és az optionsopcionálist (mivel az alapértelmezett érték meg van adva).

  • A statikus argumentumok megadása után létre kell hoznia a reguláris kifejezés egy példányát. Ez a példány kivételt okoz, ha a Regex hibás, és ez a hiba a felhasználók számára lesz jelentve.

  • DefineStaticParameters A visszahíváson belül megadhatja az argumentumok megadása után visszaadott típust.

  • Ez a kód igaz értékre van állítva HideObjectMethods , hogy az IntelliSense élménye zökkenőmentes maradjon. Ez az attribútum azt eredményezi, hogy a Equalsmegadott objektum IntelliSense-listáiban a , GetHashCode, Finalizeés GetType a tagok el lesznek tiltva.

  • A metódus alaptípusaként használja obj , de a következő példában látható módon egy Regex objektumot fog használni az ilyen típusú futtatókörnyezeti ábrázolásként.

  • A konstruktor hívása Regex akkor ArgumentException jelez, ha egy reguláris kifejezés érvénytelen. A fordító észleli ezt a kivételt, és hibaüzenetet küld a felhasználónak fordításkor vagy a Visual Studio szerkesztőjében. Ez a kivétel lehetővé teszi a reguláris kifejezések érvényesítését alkalmazás futtatása nélkül.

A fent definiált típus még nem hasznos, mert nem tartalmaz semmilyen értelmes metódust vagy tulajdonságot. Először adjon hozzá egy statikus IsMatch metódust:

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

Az előző kód meghatároz egy metódust IsMatch, amely egy sztringet vesz fel bemenetként, és visszaad egy bool. Az egyetlen trükkös rész az argumentum használata a argsInvokeCode definícióban. Ebben a példában idézőjelek listája jelenik meg, args amelyek a metódus argumentumait jelölik. Ha a metódus példánymetódus, az első argumentum az this argumentumot jelöli. Statikus metódusok esetén azonban az argumentumok csak a metódus explicit argumentumai. Vegye figyelembe, hogy a jegyzett érték típusának meg kell egyeznie a megadott visszatérési típussal (ebben az esetben bool). Azt is vegye figyelembe, hogy ez a kód a AddXmlDoc metódus használatával biztosítja, hogy a megadott módszer hasznos dokumentációval is rendelkezik, amelyet az IntelliSense-ben adhat meg.

Ezután adjon hozzá egy példányegyezési metódust. Ennek a módszernek azonban egy megadott Match típusú értéket kell visszaadnia, hogy a csoportok erősen gépelt módon legyenek elérhetők. Így először deklarálja a típust Match . Mivel ez a típus a statikus argumentumként megadott mintától függ, ezt a típust a paraméteres típusdefinícióban kell beágyazni:

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

ty.AddMember matchTy

Ezután minden csoporthoz hozzáad egy tulajdonságot az Egyezés típushoz. Futásidőben egy egyezés értékként Match jelenik meg, ezért a tulajdonságot meghatározó idézőjelnek az Groups indexelt tulajdonságot kell használnia a megfelelő csoport lekéréséhez.

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

Vegye figyelembe, hogy XML-dokumentációt ad hozzá a megadott tulajdonsághoz. Azt is vegye figyelembe, hogy egy tulajdonság akkor is olvasható, ha egy GetterCode függvény meg van adva, és a tulajdonság megírható, ha egy SetterCode függvény meg van adva, így az eredményként kapott tulajdonság csak olvasható.

Most létrehozhat egy példánymetódust, amely egy ilyen Match típusú értéket ad vissza:

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

Mivel példánymetódust hoz létre, azt a RegexTyped példányt jelöli, args[0] amelyre a metódust meghívja, és args[1] a bemeneti argumentum.

Végül adjon meg egy konstruktort, hogy a megadott típusú példányok létrehozhatók legyenek.

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

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

ty.AddMember ctor

A konstruktor csupán egy szabványos .NET Regex-példány létrehozására töröl, amely ismét egy objektumra van bekeretezve, mert obj a megadott típus törlése. Ezzel a módosítással a témakör korábbi részében megadott minta API-használat a várt módon működik. A következő kód befejeződött és végleges:

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 ()

Főbb tanulságok

Ez a szakasz azt ismertette, hogyan hozhat létre olyan típusszolgáltatót, amely a statikus paraméterein működik. A szolgáltató ellenőrzi a statikus paramétert, és annak értéke alapján biztosít műveleteket.

Helyi adatok által támogatott típusszolgáltató

Gyakran előfordulhat, hogy a típusszolgáltatók nem csak statikus paraméterek, hanem helyi vagy távoli rendszerek információi alapján szeretnének API-kat bemutatni. Ez a szakasz a helyi adatokon, például helyi adatfájlokon alapuló típusszolgáltatókat ismerteti.

Egyszerű CSV-fájlszolgáltató

Egyszerű példaként érdemes megfontolni egy típusszolgáltatót a tudományos adatok vesszővel tagolt érték (CSV) formátumban való eléréséhez. Ez a szakasz feltételezi, hogy a CSV-fájlok egy fejlécsort, majd lebegőpontos adatokat tartalmaznak, ahogyan az alábbi táblázat szemlélteti:

Távolság (mérő) Idő (másodperc)
50,0 3.7
100,0 5,2
150.0 6.4

Ez a szakasz bemutatja, hogyan adhat meg olyan típust, amellyel típustulajdonságokkal és Time típusú tulajdonsággal float<second>Distancefloat<meter> rendelkező sorokat kérhet le. Az egyszerűség kedvéért a következő feltételezések teljesülnek:

  • Az élőfejek neve vagy egység nélküli, vagy "Név (egység)" formátumú, és nem tartalmaz vesszőket.

  • Az egységek az FSharp.Data.UnitSystems.SI.UnitNames modul (F#) által definiált összes system international (SI) egység.

  • Az egységek mind egyszerűek (például mérő) és nem összetettek (például mérő/másodperc).

  • Minden oszlop lebegőpontos adatokat tartalmaz.

Egy teljesebb szolgáltató enyhítené ezeket a korlátozásokat.

Az első lépés az API megjelenésének átgondolása. info.csv Az előző táblázat tartalmával rendelkező fájl (vesszővel tagolt formátumban) a szolgáltató felhasználóinak képesnek kell lenniük az alábbi példához hasonló kód írására:

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

Ebben az esetben a fordítónak az alábbi példához hasonlóvá kell konvertálnia ezeket a hívásokat:

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

Az optimális fordításhoz a típusszolgáltatónak valós CsvFile típust kell definiálnia a típusszolgáltató szerelvényében. A típusszolgáltatók gyakran támaszkodnak néhány segédtípusra és metódusra a fontos logika burkolásához. Mivel a mértékek futásidőben törlődnek, float[] használhatja a sor törölt típusát. A fordító a különböző oszlopokat különböző mértéktípusokként kezeli. A példánk első oszlopa például típust float<meter>, a második pedig azt tartalmazza float<second>. A törölt ábrázolás azonban meglehetősen egyszerű maradhat.

Az alábbi kód a megvalósítás lényegét mutatja be.

// 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])

Jegyezze fel a megvalósításra vonatkozó alábbi szempontokat:

  • A túlterhelt konstruktorok lehetővé teszik az eredeti fájl vagy az azonos sémával rendelkezők olvasását. Ez a minta gyakori, ha helyi vagy távoli adatforrásokhoz ír típusszolgáltatót, és ez a minta lehetővé teszi, hogy a helyi fájl legyen a távoli adatok sablonja.

  • A típusszolgáltató konstruktorának átadott TypeProviderConfig értékkel feloldhatja a relatív fájlneveket.

  • A metódus használatával AddDefinitionLocation meghatározhatja a megadott tulajdonságok helyét. Ezért ha egy megadott tulajdonságot használ Go To Definition , a CSV-fájl megnyílik a Visual Studióban.

  • A típussal ProvidedMeasureBuilder megkeresheti az SI-egységeket, és létrehozhatja a megfelelő float<_> típusokat.

Főbb tanulságok

Ez a szakasz azt ismertette, hogyan hozhat létre típusszolgáltatót egy helyi adatforráshoz egy egyszerű sémával, amely magában az adatforrásban található.

Továbblépés

A következő szakaszok további tanulmányozásra vonatkozó javaslatokat tartalmaznak.

Tekintse meg a törölt típusok lefordított kódját

Ha képet szeretne adni arról, hogy a típusszolgáltató hogyan felel meg a kibocsátott kódnak, tekintse meg a következő függvényt a HelloWorldTypeProvider jelen témakör korábbi részében használt függvény használatával.

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

Íme egy kép az eredményként kapott kódról, amely ildasm.exe használatával bontható le:

.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

Ahogy a példa is mutatja, a típus Type1 és a InstanceProperty tulajdonság összes említése törölve lett, így csak a futtatókörnyezet-típusok műveleteit hagyta.

Típusszolgáltatók tervezési és elnevezési konvenciói

A típusszolgáltatók létrehozásakor kövesse az alábbi konvenciókat.

A Csatlakozás tivitási protokollok szolgáltatói általában az adat- és szolgáltatáskapcsolati protokollok legtöbb szolgáltatói DLL-jének (például OData- vagy SQL-kapcsolatnak) a végén vagy TypeProvidersvégén TypeProvider kell végződnie. Használjon például a következő sztringhez hasonló DLL-nevet:

Fabrikam.Management.BasicTypeProviders.dll

Győződjön meg arról, hogy a megadott típusok a megfelelő névtér tagjai, és adja meg a megvalósított kapcsolati protokollt:

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

Általános kódolási segédprogram-szolgáltatók. Egy olyan segédprogramtípus-szolgáltató esetében, mint a normál kifejezések esetében, a típusszolgáltató egy alapkódtár része lehet, ahogyan az alábbi példa mutatja:

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

Ebben az esetben a megadott típus a normál .NET tervezési konvencióknak megfelelő helyen jelenik meg:

  open Fabrikam.Core.Text.RegexTyped

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

Önálló adatforrások. Egyes típusszolgáltatók egyetlen dedikált adatforráshoz csatlakoznak, és csak adatokat biztosítanak. Ebben az esetben el kell dobnia az TypeProvider utótagot, és a .NET-elnevezéshez normál konvenciókra van szükség:

#r "Fabrikam.Data.Freebase.dll"

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

További információkért tekintse meg a GetConnection jelen témakör későbbi részében ismertetett tervezési konvenciót.

Tervezési minták típusszolgáltatókhoz

Az alábbi szakaszok a típusszolgáltatók létrehozásakor használható tervezési mintákat ismertetik.

A Get Csatlakozás ion tervezési mintája

A legtöbb típusszolgáltatót úgy kell írni, hogy a GetConnection FSharp.Data.TypeProviders.dll típusszolgáltatói által használt mintát használják, ahogy az alábbi példa is mutatja:

#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

Írja be a távoli adatok és szolgáltatások által támogatott szolgáltatókat

A távoli adatok és szolgáltatások által támogatott típusszolgáltató létrehozása előtt figyelembe kell vennie a csatlakoztatott programozással járó problémák egy sorát. Ezek a problémák a következő szempontokat foglalják magukban:

  • sémaleképezés

  • élősség és érvénytelenítés sémamódosítás jelenlétében

  • séma gyorsítótárazása

  • adatelérési műveletek aszinkron implementációi

  • támogató lekérdezések, beleértve a LINQ-lekérdezéseket

  • hitelesítő adatok és hitelesítés

Ez a témakör nem vizsgálja tovább ezeket a problémákat.

További szerzői technikák

Ha saját típusszolgáltatókat ír, érdemes lehet az alábbi további technikákat használnia.

Típusok és tagok igény szerinti létrehozása

A ProvidedType API késleltette az AddMember verzióit.

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

Ezek a verziók igény szerinti típusok létrehozására szolgálnak.

Tömbtípusok és általános típus-példányok biztosítása

A megadott tagokat (amelyeknek az aláírásai tömbtípusokat, byref-típusokat és általános típusok példányait tartalmazzák) a normál MakeArrayType, MakePointerTypeés MakeGenericType az összes példányon Type, beleértve a ProvidedTypeDefinitions.

Feljegyzés

Bizonyos esetekben előfordulhat, hogy a segédet kell használnia a következőben ProvidedTypeBuilder.MakeGenericType: . További részletekért tekintse meg a Type Provider SDK dokumentációját .

Mértékegységi széljegyzetek megadása

A ProvidedTypes API segítséget nyújt a mértékjegyzetek megadásához. A típus float<kg>megadásához például használja a következő kódot:

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

A típus Nullable<decimal<kg/m^2>>megadásához használja a következő kódot:

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

A Project-Local vagy a Script-Local erőforrások elérése

A típusszolgáltató minden példánya megadhat értéket az TypeProviderConfig építés során. Ez az érték tartalmazza a szolgáltató "megoldási mappáját" (vagyis a fordítás projektmappáját vagy a szkriptet tartalmazó könyvtárat), a hivatkozott szerelvények listáját és egyéb információkat.

Érvénytelenítés

A szolgáltatók érvénytelenítési jeleket állíthatnak fel, hogy értesítsék az F# nyelvi szolgáltatást arról, hogy a sémafeltevések módosulhattak. Érvénytelenítés esetén újra létrejön egy típusellenőrzés, ha a szolgáltatót a Visual Studióban üzemelteti. A rendszer figyelmen kívül hagyja ezt a jelet, ha a szolgáltató az F# Interactive vagy az F# Fordító (fsc.exe) szolgáltatásban üzemel.

Gyorsítótárazási séma adatai

A szolgáltatóknak gyakran gyorsítótáraznia kell a sémainformációkhoz való hozzáférést. A gyorsítótárazott adatokat statikus paraméterként vagy felhasználói adatokként megadott fájlnév használatával kell tárolni. A séma gyorsítótárazására példa a LocalSchemaFile szerelvény típusszolgáltatóinak FSharp.Data.TypeProviders paramétere. A szolgáltatók implementálásánál ez a statikus paraméter arra utasítja a típusszolgáltatót, hogy a megadott helyi fájl sémaadatait használja ahelyett, hogy a hálózaton keresztül fér hozzá az adatforráshoz. A gyorsítótárazott sémainformációk használatához a statikus paramétert ForceUpdate is be kell állítania a következőre false: . Hasonló technikával engedélyezheti az online és offline adathozzáférést.

Háttérrendszer szerelvénye

Egy vagy .exe több .dll fájl fordításakor a létrehozott típusok biztonsági .dll fájlja statikusan kapcsolódik az eredményül kapott szerelvényhez. Ez a hivatkozás úgy jön létre, hogy a köztes nyelv (IL) típusdefinícióit és a háttérszerelvényből származó felügyelt erőforrásokat a végső szerelvénybe másolja. Az F# Interactive használatakor a rendszer nem másolja ki a háttérfájlt .dll, hanem közvetlenül az F# Interaktív folyamatba tölti be.

Kivételek és diagnosztika típusszolgáltatóktól

A megadott típusok összes tagjának minden használata kivételeket okozhat. Minden esetben, ha egy típusszolgáltató kivételt jelez, a gazdagépfordító a hibát egy adott típusszolgáltatóhoz rendeli.

  • A típusszolgáltatói kivételek soha nem eredményezhetnek belső fordítóhibákat.

  • A típusszolgáltatók nem tudnak figyelmeztetéseket jelenteni.

  • Ha egy típusszolgáltató az F# fordítóban, egy F# fejlesztői környezetben vagy az F# Interactive-ban van üzemeltetve, a rendszer minden kivételt ki fog kapni a szolgáltatótól. Az Üzenet tulajdonság mindig a hibaszöveg, és nem jelenik meg veremkövetés. Ha kivételt szeretne tenni, a következő példákat vetheti fel: System.NotSupportedException, System.IO.IOException, System.Exception.

Generált típusok megadása

Ez a dokumentum eddig a törölt típusok megadását ismertette. Az F# típusszolgáltatói mechanizmusával generált típusokat is megadhat, amelyek valós .NET-típusdefiníciókként lesznek hozzáadva a felhasználói programhoz. A létrehozott típusokra típusdefinícióval kell hivatkoznia.

open Microsoft.FSharp.TypeProviders

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

Az F# 3.0 kiadás részét képező ProvidedTypes-0.2 segédkód csak korlátozottan támogatja a létrehozott típusok biztosítását. A következő utasításoknak igaznak kell lenniük egy létrehozott típusdefiníció esetében:

  • isErased beállításnak a következőre kell állítania: false.

  • A generált típust hozzá kell adni egy újonnan létrehozott ProvidedAssembly()típushoz, amely a létrehozott kódtöredékek tárolóját jelöli.

  • A szolgáltatónak rendelkeznie kell egy olyan szerelvénysel, amely rendelkezik egy .NET-.dll-fájllal, amely egy megfelelő .dll fájllal rendelkezik a lemezen.

Szabályok és korlátozások

Amikor típusszolgáltatókat ír, tartsa szem előtt az alábbi szabályokat és korlátozásokat.

A megadott típusoknak elérhetőnek kell lenniük

Minden megadott típusnak elérhetőnek kell lennie a nem beágyazott típusok közül. A nem beágyazott típusok a konstruktor hívásában vagy a TypeProviderForNamespaces hívásban jelennek meg AddNamespace. Ha például a szolgáltató egy típust StaticClass.P : Tad meg, akkor meg kell győződnie arról, hogy a T nem beágyazott típus, vagy egy alá van ágyazva.

Egyes szolgáltatók például olyan statikus osztályokkal rendelkeznek, amelyek DataTypes ilyen típusokat T1, T2, T3, ... tartalmaznak. Ellenkező esetben a hiba azt jelzi, hogy az A szerelvény T típusára mutató hivatkozás található, de a típus nem található az adott szerelvényben. Ha ez a hiba jelenik meg, ellenőrizze, hogy az összes altípus elérhető-e a szolgáltatótípusokból. Megjegyzés: Ezeket T1, T2, T3... a típusokat menet közbeni típusnak nevezzük. Ne felejtse el akadálymentes névtérbe vagy szülőtípusba helyezni őket.

A típusszolgáltatói mechanizmus korlátozásai

Az F# típusszolgáltatói mechanizmusa a következő korlátozásokkal rendelkezik:

  • Az F# típusszolgáltatók mögöttes infrastruktúrája nem támogatja a megadott általános típusokat vagy általános metódusokat.

  • A mechanizmus nem támogatja a statikus paraméterekkel rendelkező beágyazott típusokat.

Fejlesztési Tippek

A fejlesztési folyamat során hasznosnak találhatja a következő tippeket:

A Visual Studio két példányának futtatása

Fejlesztheti a típusszolgáltatót az egyik példányban, és tesztelheti a szolgáltatót a másikban, mert a teszt IDE zárolja a .dll fájlt, amely megakadályozza a típusszolgáltató újraépítését. Ezért be kell zárnia a Visual Studio második példányát, amíg a szolgáltató az első példányban van felépítve, majd újra meg kell nyitnia a második példányt a szolgáltató létrehozása után.

Típusszolgáltatók hibakeresése fsc.exe meghívásával

Típusszolgáltatókat az alábbi eszközökkel hívhat meg:

  • fsc.exe (Az F# parancssori fordító)

  • fsi.exe (Az F# interaktív fordító)

  • devenv.exe (Visual Studio)

A típusszolgáltatók hibakeresése gyakran a fsc.exe egy tesztszkriptfájlban (például script.fsx) történő használatával lehetséges. A parancssorból elindíthat egy hibakeresőt.

devenv /debugexe fsc.exe script.fsx

Használhatja a print-to-stdout naplózást.

Lásd még