Megosztás a következőn keresztül:


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éldául:

  • 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 examples könyvtárában található 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.

Mielőtt újrafordítaná a szolgáltatót, ellenőrizze, hogy bezárta-e a Visual Studio és az F# Interactive összes példányát, amelyek a szolgáltatói DLL-t használják. 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. A Debug menü megnyitásával, majd a Exceptions választásával, vagy a Ctrl+Alt+E billentyűkombináció használatával is beállíthatja az első esélyű kivétel elkapását, ezzel megnyitva a Exceptions párbeszédpanelt. 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 assembly attribútumot, amely azt jelzi, hogy típus-szolgáltató 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 obj jelzi, 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 az assemblyt é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ó halasztott, vagyis igény szerint lesz kiszámítva, ha a gazdagép fordítóprogramnak 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# kifejezést használ, amely a kódot jelöli, amit a gazdagépfordító a tulajdonság lekéréséhez generál. További információk a kódidézetekről a Kódidézetek (F#) című témakörben találhatók.

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 alapul szolgáló "Az objektumadatok" adatokkal kerül létrehozásra." 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 ismét egy F# kód idézetet ad vissza, amely azt a kódot képviseli, amelyet a gazdagép fordító generált a metódus meghívására. 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# kifejezést ad vissza, amely meghatározza a gazdagép fordítója által a tulajdonság lekéréséhez generált kódot. A InvokeCode függvény GetterCode-hoz hasonlóan idézőjelet ad vissza. A gazdagép fordító egy argumentumlistával hívja meg ezt a függvényt. Ebben az esetben az argumentumok csak az egyetlen kifejezést tartalmazzák, amely azt a példányt jelöli, amelyen a getter hívása történik, és ezt a args[0] segítségével érheted el. A GetterCode megvalósítása az eredmény idézetébe beilleszti a törölt típusnál obj, és egy típuskonverziót alkalmaz, hogy kielégítse a fordító mechanizmusát, amely ellenőrzi, hogy az objektum egy sztring-e. A makeOneProvidedType következő része egy paraméteres példánymetódust biztosít.

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ű típus-semantikák nem kritikusak az információs tér gyakorlati használatához.

  • Amikor egy olyan nagy és összekapcsolt információs térhez készít szolgáltatót, hogy technikailag nem kivitelezhető valós .NET-típusok létrehozása az információs térhez.

Ebben a példában minden megadott típus törlődik, és helyette a obj típus kerül beírásra, és a fordított kódban minden típus használata obj típus formájában jelenik meg. 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 egy érvénytelen kasztolási kivétel keletkezhet, amikor az objektumot használják. 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, amikor törölt típusokat használ típusszolgáltatójában, vagy olyan szolgáltatót vesz igénybe, amely törölt típusokat biztosít. A törölt típus nem rendelkezik valódi .NET-típussal. Ezért nem tudja pontosan visszatükrözni a típust, és alááshatja a törölt típusokat, ha futásidejű típuskonverziókat és más technikákat használ, amelyek a futásidejű típus szemantikájának pontosságára támaszkodnak. A törölt típusok manipulációja gyakran eredményez típuskonverziós kivételeket futásidejű környezetben.

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 tudja érni egy törölt típus példányait, vegye figyelembe az alábbi kérdéseket:

Mit jelent a megadott típus törlése?

  • 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 egyszerűen egy meglévő .NET-típus köré biztosítasz egy erősen típusos burkolót, gyakran van értelme, hogy a típus elrejtse ezt a típust, az ilyen típusú példányokat reprezentációként használja, vagy mindkettőt. 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 meghatározott tagok ezután képesek ilyen típusú példányokat létrehozni:

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

Ebben az esetben (opcionálisan) ezt a típust használhatja típushomályosításként azáltal, hogy ezt a típust adja meg baseType-ként a ProvidedTypeDefinition konstrukciója során.

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.

Típusellenőrzött Regex-szolgáltatás

Tegyük fel, hogy olyan típusszolgáltatót szeretne implementálni a reguláris kifejezésekhez, amely a .NET Regex könyvtárakat egy olyan felületen burkolja, amely a következő fordítási időben nyújtott garanciákat biztosítja:

  • 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"

Jegyezze fel a következő pontokat:

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

Jegyezze fel a következő pontokat:

  • 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 beállítja HideObjectMethods-t igazra, 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 Regex konstruktort hívva, egy ArgumentException kivételt dob, 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, hogy hogyan kell az args argumentumot a InvokeCode definícióban használni. 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 hozzáad egy tulajdonságot az Egyezés típushoz minden csoporthoz. Futásidőben egy egyezés 'Match' értékként jelenik meg, ezért a tulajdonság meghatározásához használt idézőjelnek az Groups indexelt tulajdonságot kell alkalmaznia a megfelelő csoport elé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, a args[0] jelöli azt a RegexTyped példányt, amelyen a metódust meghívják, és a 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át eredményezi, amely ismét egy objektumra van becsomagolva, mert obj a megadott típus törlését jelzi. 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éter) 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 olyan sorokat kérhet le, amelyeknek van egy Distance típusú tulajdonsága és egy float<meter> típusú tulajdonsága Time. Az egyszerűség kedvéért a következő feltételezések teljesülnek:

  • Fejlécnevek formázása vagy egység nélküli, vagy "Név (egység)", és nem tartalmaz vesszőket.

  • Az egységek a FSharp.Data.UnitSystems.SI.UnitNames modul (F#) által definiált összes nemzetközi mértékegység-rendszer (SI) egységek.

  • 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, egy float[]-t használhat törölt típusként egy sorhoz. A fordítóprogram 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.exehaszná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 futásidejű típusokkal kapcsolatos műveletek maradtak.

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.

Csatlakozási protokollok szolgáltatói Általánosságban elmondható, hogy az adat- és szolgáltatáskapcsolati protokollok legtöbb szolgáltatói DLL-jének neve (például OData- vagy SQL-kapcsolatoknál) TypeProvider vagy TypeProviders végződéssel kell, hogy rendelkezzen. 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 GetConnection tervezési mintája

A legtöbb típusszolgáltatót úgy kell írni, hogy az GetConnection FSharp.Data.TypeProviders.dlltí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

Távoli adatok és szolgáltatások által támogatott típus-szolgáltatók

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, hasznos 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 különböző típusú terek igény szerinti létrehozására szolgálnak.

Tömbtípusok és generikus típuskiterjesztések biztosítása

A megadott tagokat (amelyek 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 használatával hozod létre bármely Type példányra, beleértve a ProvidedTypeDefinitions példányokat is.

Megjegyzé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 |]

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

Az egyes típusszolgáltató példányai az TypeProviderConfig építés során is megkaphatnak egy értéket. 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 egy típusellenőrzést újra végrehajtanak, ha a szolgáltató a Visual Studio-ban fut. 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 van üzemeltetve.

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

.dll vagy .exe fájl fordításakor a generált típusokhoz tartozó háttérfájl, a .dll, statikusan kapcsolódik a létrejövő egységbe. 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 diagnosztikai adatok a 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 küldeni.

  • 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 hibaüzenet, és veremnyom sem jelenik meg. 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-t be kell állítani false-re.

  • 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 tényleges háttérfájllal és egy megfelelő .dll fájllal a lemezen.

Szabályok és korlátozások

Amikor típusszolgáltatókat ír, vegye figyelembe az alábbi szabályokat és korlátozásokat.

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

Minden megadott típusnak közvetlenül elérhetőnek kell lennie a nem beágyazott típusokból. A nem beágyazott típusokat a TypeProviderForNamespaces konstruktor hívásakor vagy a AddNamespace hívásakor adjuk meg. Ha például a szolgáltató egy típust StaticClass.P : T ad meg, akkor biztosítania kell arról, hogy a T vagy nem egy beágyazott típus, vagy egy másik típusba 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.

A fsc.exe meghívásával végezze el a típusszolgáltatók hibakeresését.

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

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

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

  • devenv.exe (Visual Studio)

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

devenv /debugexe fsc.exe script.fsx

Használhatja a `print` parancsot a `stdout`-ba történő naplózáshoz.

Lásd még