Sdílet prostřednictvím


Kurz: Vytvoření zprostředkovatele typu

Mechanismus zprostředkovatele typů v jazyce F# je významnou součástí podpory pro bohaté programování informací. Tento kurz vysvětluje, jak vytvořit vlastní poskytovatele typů tím, že vás provede vývojem několika jednoduchých zprostředkovatelů typů, abyste si ukázali základní koncepty. Další informace o mechanismu zprostředkovatele typů v jazyce F# najdete v tématu Zprostředkovatelé typů.

Ekosystém F# obsahuje řadu poskytovatelů typů pro běžně používané internetové a podnikové datové služby. Příklad:

  • FSharp.Data obsahuje zprostředkovatele typů pro formáty dokumentů JSON, XML, CSV a HTML.

  • SwaggerProvider obsahuje dva zprostředkovatelé generující typy, kteří generují objektový model a klienty HTTP pro rozhraní API popsaná schématy OpenApi 3.0 a Swagger 2.0.

  • FSharp.Data.SqlClient obsahuje sadu zprostředkovatelů typů pro kompilační vkládání T-SQL v jazyce F#.

Můžete vytvořit vlastní zprostředkovatele typů nebo můžete odkazovat na zprostředkovatele typů, které vytvořili jiní uživatelé. Vaše organizace může mít například datovou službu, která poskytuje velký a rostoucí počet pojmenovaných datových sad, z nichž každá má vlastní stabilní schéma dat. Můžete vytvořit zprostředkovatele typu, který čte schémata a prezentuje aktuální datové sady programátorům silným typem.

Než začnete

Mechanismus poskytovatele typů je primárně určený pro vkládání stabilních prostorů pro informace o datech a službách do programovacího prostředí jazyka F#.

Tento mechanismus není určený pro vkládání informačních prostorů, jejichž schéma se během provádění programu mění způsoby, které jsou relevantní pro logiku programu. Mechanismus navíc není určený pro metaprogramování uvnitř jazyka, i když tato doména obsahuje některá platná použití. Tento mechanismus byste měli použít pouze tam, kde je to nutné a kde vývoj poskytovatele typu přináší velmi vysokou hodnotu.

Měli byste se vyhnout zápisu zprostředkovatele typu, kde není schéma k dispozici. Stejně tak byste se měli vyhnout zápisu poskytovatele typů, ve kterém stačí běžná (nebo dokonce existující) knihovna .NET.

Než začnete, můžete se zeptat na následující otázky:

  • Máte schéma pro zdroj informací? Pokud ano, jaké je mapování do systému typů F# a .NET?

  • Můžete použít existující (dynamicky typované) rozhraní API jako výchozí bod pro vaši implementaci?

  • Budete vy a vaše organizace mít dostatek použití poskytovatele typu, aby bylo vhodné psát? Vyhovuje vám normální knihovna .NET?

  • Kolik se změní vaše schéma?

  • Změní se během kódování?

  • Změní se mezi programovacími relacemi?

  • Změní se během provádění programu?

Zprostředkovatelé typů jsou nejvhodnější pro situace, kdy je schéma stabilní za běhu a během doby životnosti zkompilovaného kódu.

Zprostředkovatel jednoduchého typu

Tato ukázka je Samples.HelloWorldTypeProvider, podobně jako ukázky v examples adresáři sady SDK zprostředkovatele typů F#. Zprostředkovatel zpřístupní "mezeru typu", která obsahuje 100 mazaných typů, jak ukazuje následující kód pomocí syntaxe podpisu jazyka F# a vynechá podrobnosti pro všechny kromě Type1. Další informace o mazaných typech naleznete v části Podrobnosti o vymazání zadaných typů dále v tomto tématu.

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

Všimněte si, že sada typů a členů je staticky známá. Tento příklad nevyuží schopnost poskytovatelů poskytovat typy, které závisí na schématu. Implementace zprostředkovatele typu je uvedena v následujícím kódu a podrobnosti jsou popsány v dalších částech tohoto tématu.

Upozorňující

Mezi tímto kódem a online ukázkami můžou být rozdíly.

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

Pokud chcete použít tohoto zprostředkovatele, otevřete samostatnou instanci sady Visual Studio, vytvořte skript jazyka F# a potom pomocí #r přidejte odkaz na zprostředkovatele z vašeho skriptu, jak ukazuje následující kód:

#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

Potom vyhledejte typy v Samples.HelloWorldTypeProvider oboru názvů, který vygeneroval zprostředkovatel typu.

Před opětovnou kompilací zprostředkovatele se ujistěte, že jste zavřeli všechny instance sady Visual Studio a F# Interactive, které používají knihovnu DLL zprostředkovatele. V opačném případě dojde k chybě sestavení, protože výstupní knihovna DLL bude uzamčena.

Pokud chcete tohoto zprostředkovatele ladit pomocí tiskových příkazů, vytvořte skript, který zpřístupňuje problém se zprostředkovatelem, a pak použijte následující kód:

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

Pokud chcete tohoto poskytovatele ladit pomocí sady Visual Studio, otevřete příkazový řádek pro vývojáře pro Visual Studio s přihlašovacími údaji správce a spusťte následující příkaz:

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

Alternativně otevřete Visual Studio, otevřete nabídku Ladění, zvolte Debug/Attach to process…a připojte se k jinému devenv procesu, ve kterém upravujete skript. Pomocí této metody můžete snadněji cílit na konkrétní logiku poskytovatele typů tím, že interaktivně zadáte výrazy do druhé instance (s plnou technologií IntelliSense a dalšími funkcemi).

Ladění just My Code můžete zakázat, abyste lépe identifikovali chyby ve vygenerovaném kódu. Informace o povolení nebo zakázání této funkce naleznete v tématu Procházení kódu pomocí ladicího programu. Můžete také nastavit první náhodnou výjimku zachycení tak, že otevřete Debug nabídku a pak zvolíte Exceptions nebo zvolíte klávesy Ctrl+Alt+E a otevře Exceptions se dialogové okno. V dialogovém okně v části Common Language Runtime Exceptionszaškrtněte Thrown políčko.

Implementace zprostředkovatele typu

Tato část vás provede hlavními částmi implementace zprostředkovatele typu. Nejprve definujete typ pro samotného zprostředkovatele vlastního typu:

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

Tento typ musí být veřejný a musíte ho označit atributem TypeProvider , aby kompilátor rozpoznal zprostředkovatele typu, když samostatný projekt F# odkazuje na sestavení, které tento typ obsahuje. Parametr konfigurace je volitelný a pokud je k dispozici, obsahuje informace o kontextové konfiguraci pro instanci zprostředkovatele typu, kterou kompilátor jazyka F# vytvoří.

Dále implementujete rozhraní ITypeProvider . V tomto případě použijete TypeProviderForNamespaces typ z ProvidedTypes rozhraní API jako základní typ. Tento pomocný typ může poskytnout konečnou kolekci dychtivě poskytovaných oborů názvů, z nichž každý přímo obsahuje konečný počet pevných a dychtivých typů. V tomto kontextu poskytovatel dychtivě generuje typy, i když nejsou potřeba nebo použity.

inherit TypeProviderForNamespaces(config)

Dále definujte místní privátní hodnoty, které určují obor názvů pro zadané typy, a vyhledejte samotné sestavení zprostředkovatele typů. Toto sestavení se použije později jako logický nadřazený typ mazaných typů, které jsou k dispozici.

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

Dále vytvořte funkci, která poskytne jednotlivé typy Type1... Zadejte 100. Tato funkce je podrobněji vysvětlena dále v tomto tématu.

let makeOneProvidedType (n:int) = …

Dále vygenerujte 100 zadaných typů:

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

Dále přidejte typy jako zadaný obor názvů:

do this.AddNamespace(namespaceName, types)

Nakonec přidejte atribut sestavení, který označuje, že vytváříte knihovnu DLL zprostředkovatele typu:

[<assembly:TypeProviderAssembly>]
do()

Poskytnutí jednoho typu a jeho členů

Funkce makeOneProvidedType dělá skutečnou práci poskytování jednoho z typů.

let makeOneProvidedType (n:int) =
…

Tento krok vysvětluje implementaci této funkce. Nejprve vytvořte zadaný typ (například Typ1, když n = 1 nebo Type57, když 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>)

Měli byste si uvědomit následující body:

  • Tento zadaný typ se vymaže. Vzhledem k tomu, že je základní typ obj, instance se zobrazí jako hodnoty typu obj v kompilovaném kódu.

  • Při zadání nevnořeného typu je nutné zadat sestavení a obor názvů. Pro vymazání typů by sestavení mělo být samotné sestavení zprostředkovatele typů.

Dále do typu přidejte dokumentaci XML. Tato dokumentace je zpožděná, tj. počítá se na vyžádání, pokud ji kompilátor hostitele potřebuje.

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

Dále do typu přidáte zadanou statickou vlastnost:

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

Získání této vlastnosti se vždy vyhodnotí jako řetězec "Hello!". Vlastnost GetterCode používá uvozovku jazyka F#, který představuje kód, který kompilátor hostitele generuje pro získání vlastnosti. Další informace o uvozovkách najdete v části Uvozovky kódu (F#).

Přidejte do vlastnosti dokumentaci XML.

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

Nyní připojte zadanou vlastnost k zadanému typu. Zadaný člen musíte připojit k jednomu a pouze jednomu typu. V opačném případě nebude člen nikdy přístupný.

t.AddMember staticProp

Teď vytvořte zadaný konstruktor, který nepřijímá žádné parametry.

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

Konstruktor InvokeCode vrátí uvozovku jazyka F#, který představuje kód, který kompilátor hostitele generuje při zavolání konstruktoru. Můžete například použít následující konstruktor:

new Type10()

Instance poskytnutého typu se vytvoří s podkladovými daty "Data objektu". Citovaný kód obsahuje převod na obj , protože tento typ je vymazáním tohoto zadaného typu (jak jste určili při deklaraci zadaného typu).

Přidejte do konstruktoru dokumentaci XML a přidejte zadaný konstruktor do zadaného typu:

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

t.AddMember ctor

Vytvořte druhý zadaný konstruktor, který přebírá jeden parametr:

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

Konstruktor InvokeCode znovu vrátí uvozovku jazyka F#, který představuje kód, který kompilátor hostitele vygeneroval pro volání metody. Můžete například použít následující konstruktor:

new Type10("ten")

Instance poskytnutého typu se vytvoří s podkladovými daty "deset". Možná jste si už všimli, že InvokeCode funkce vrací uvozovky. Vstupem této funkce je seznam výrazů, jeden pro parametr konstruktoru. V tomto případě je k dispozici args[0]výraz, který představuje hodnotu jednoho parametru . Kód volání konstruktoru převede návratovou hodnotu na odstraněný typ obj. Po přidání druhého poskytnutého konstruktoru do typu vytvoříte zadanou vlastnost instance:

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

Získání této vlastnosti vrátí délku řetězce, což je objekt reprezentace. Vlastnost GetterCode vrátí uvozovku jazyka F#, která určuje kód, který kompilátor hostitele generuje pro získání vlastnosti. GetterCode Podobně jako InvokeCodefunkce vrátí uvozovku. Kompilátor hostitele volá tuto funkci se seznamem argumentů. V tomto případě argumenty zahrnují pouze jeden výraz, který představuje instanci, na které je volána getter, ke kterému můžete přistupovat pomocí args[0]. Implementace GetterCode poté splices do výsledné uvozovky u mazaného typu obja přetypování se používá k uspokojení mechanismu kompilátoru pro kontrolu typů, že objekt je řetězec. Další část makeOneProvidedType poskytuje metodu instance s jedním parametrem.

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

Nakonec vytvořte vnořený typ, který obsahuje 100 vnořených vlastností. Vytvoření tohoto vnořeného typu a jeho vlastností je zpožděné, tj. vypočítané na vyžádání.

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

Podrobnosti o odstraněných zadaných typech

Příklad v této části poskytuje pouze typy, které jsou zvlášť užitečné v následujících situacích:

  • Při psaní zprostředkovatele pro informační prostor, který obsahuje pouze data a metody.

  • Při psaní poskytovatele, kde přesná sémantika typu runtime není pro praktické použití informačního prostoru kritická.

  • Při psaní poskytovatele pro informační prostor, který je tak velký a propojený, že není technicky proveditelné generovat skutečné typy .NET pro informační prostor.

V tomto příkladu se každý zadaný typ vymaže, aby typ obja všechny použití typu se zobrazí jako typ obj v zkompilovaném kódu. Ve skutečnosti jsou podkladové objekty v těchto příkladech řetězce, ale typ se zobrazí jako System.Object v zkompilovaném kódu .NET. Stejně jako u všech použití mazání typu můžete použít explicitní boxing, rozbalení a přetypování k podvertování mazaných typů. V tomto případě může při použití objektu dojít k výjimce přetypování, která není platná. Modul runtime zprostředkovatele může definovat vlastní privátní typ reprezentace, který pomáhá chránit před nepravdivými reprezentacemi. V jazyce F# není možné definovat mazané typy. Je možné vymazat pouze zadané typy. Musíte pochopit důsledky, a to jak praktické, tak i sémantické, použití buď odstraněných typů pro poskytovatele typu, nebo poskytovatele, který poskytuje odstraněné typy. Vymazání typu nemá žádný skutečný typ .NET. Proto nelze přesnou reflexi typu a pokud používáte přetypování za běhu a další techniky, které spoléhají na přesnou sémantiku typu modulu runtime, můžete vymazat typy. Podverze mazaných typů často vede k výjimkám přetypování typů za běhu.

Volba reprezentací pro vymazání zadaných typů

U některých použití vymazání zadaných typů není vyžadováno žádné vyjádření. Vymazání zadaného typu může například obsahovat pouze statické vlastnosti a členy a žádné konstruktory a žádné metody nebo vlastnosti by vrátily instanci typu. Pokud se můžete dostat k instancím zadaného typu s vymazáním, musíte zvážit následující otázky:

Co je vymazání poskytnutého typu?

  • Vymazání zadaného typu je způsob, jakým se typ zobrazuje v zkompilovaném kódu .NET.

  • Vymazání poskytnutého typu odstraněné třídy je vždy prvním nesmazatým základním typem v řetězci dědičnosti typu.

  • Vymazání poskytnutého typu smazaného rozhraní je vždy System.Object.

Jaké jsou reprezentace zadaného typu?

  • Sada možných objektů pro vymazat zadaný typ se nazývá jeho reprezentace. V příkladu v tomto dokumentu jsou reprezentace všech mazaných zadaných typů Type1..Type100 vždy řetězcové objekty.

Všechny reprezentace zadaného typu musí být kompatibilní s vymazáním zadaného typu. (V opačném případě kompilátor jazyka F# zobrazí chybu pro použití zprostředkovatele typu nebo neověřený kód .NET, který není platný, se vygeneruje. Poskytovatel typu není platný, pokud vrátí kód, který poskytuje reprezentaci, která není platná.)

Reprezentaci pro poskytnuté objekty můžete zvolit pomocí některého z následujících přístupů, z nichž obě jsou velmi běžné:

  • Pokud jednoduše poskytujete obálku silného typu nad existujícím typem .NET, často dává smysl, aby se typ mazal na tento typ, používal instance tohoto typu jako reprezentace nebo obojí. Tento přístup je vhodný, pokud většina existujících metod tohoto typu stále dává smysl při použití verze silného typu.

  • Pokud chcete vytvořit rozhraní API, které se výrazně liší od jakéhokoli existujícího rozhraní .NET API, je vhodné vytvořit typy modulu runtime, které budou vymazáním a reprezentací typu pro zadané typy.

Příklad v tomto dokumentu používá řetězce jako reprezentaci zadaných objektů. Často může být vhodné použít jiné objekty pro reprezentaci. Slovník můžete například použít jako tašku vlastností:

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

Jako alternativu můžete definovat typ ve zprostředkovateli typu, který se použije za běhu k vytvoření reprezentace spolu s jednou nebo více operacemi runtime:

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

Poskytnuté členy pak mohou vytvářet instance tohoto typu objektu:

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

V tomto případě můžete tento typ (volitelně) použít jako vymazání typu zadáním tohoto typu jako při baseType vytváření ProvidedTypeDefinition:

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

Klíčové lekce

Předchozí část vysvětluje, jak vytvořit jednoduchého zprostředkovatele typu mazání, který poskytuje rozsah typů, vlastností a metod. Tato část také vysvětlila koncept vymazání typu, včetně některých výhod a nevýhod poskytování mazaných typů od poskytovatele typů a popisovaných reprezentací mazaných typů.

Zprostředkovatel typu, který používá statické parametry

Možnost parametrizovat zprostředkovatele typů statickými daty umožňuje mnoho zajímavých scénářů, a to i v případech, kdy poskytovatel nepotřebuje přístup k žádným místním nebo vzdáleným datům. V této části se seznámíte s některými základními technikami pro vytvoření takového poskytovatele.

Typ zaškrtnutý zprostředkovatel regulárních výrazů

Představte si, že chcete implementovat zprostředkovatele typu pro regulární výrazy, které zabalí knihovny .NET Regex do rozhraní, které poskytuje následující záruky doby kompilace:

  • Ověření platnosti regulárního výrazu

  • Poskytuje pojmenované vlastnosti pro shody založené na názvech skupin v regulárním výrazu.

V této části se dozvíte, jak pomocí zprostředkovatelů typů vytvořit RegexTyped typ, který parametrizuje vzor regulárního výrazu, aby tyto výhody poskytoval. Kompilátor oznámí chybu, pokud zadaný vzor není platný, a poskytovatel typu může extrahovat skupiny ze vzoru, abyste k nim měli přístup pomocí pojmenovaných vlastností na shodách. Při návrhu poskytovatele typu byste měli zvážit, jak by jeho vystavené rozhraní API mělo vypadat koncovým uživatelům a jak se tento návrh přeloží na kód .NET. Následující příklad ukazuje, jak pomocí tohoto rozhraní API získat komponenty směrového kódu oblasti:

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"

Následující příklad ukazuje, jak poskytovatel typů překládá tato volání:

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"

Mějte na paměti následující body:

  • Standardní typ Regex představuje parametrizovaný RegexTyped typ.

  • Konstruktor RegexTyped způsobí volání konstruktoru Regex a předá argument statického typu pro vzor.

  • Výsledky Match metody jsou reprezentovány standardním Match typem.

  • Každá pojmenovaná skupina má zadanou vlastnost a přístup k této vlastnosti vede k použití indexeru v kolekci shody Groups .

Následující kód je jádrem logiky pro implementaci takového zprostředkovatele a tento příklad vynechá přidání všech členů k zadanému typu. Informace o každém přidaném členu najdete v příslušné části dále v tomto tématu.

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

Mějte na paměti následující body:

  • Zprostředkovatel typu má dva statické parametry: patternpovinné , a options, které jsou volitelné (protože je zadána výchozí hodnota).

  • Po zadání statických argumentů vytvoříte instanci regulárního výrazu. Tato instance vyvolá výjimku, pokud je regex poškozený a tato chyba bude hlášena uživatelům.

  • V rámci zpětného DefineStaticParameters volání definujete typ, který se vrátí po zadání argumentů.

  • Tento kód se nastaví HideObjectMethods na true, aby prostředí IntelliSense zůstalo zjednodušené. Tento atribut způsobí Equalspotlačení , , FinalizeGetHashCodea GetType členů ze seznamů IntelliSense pro zadaný objekt.

  • Použijete obj jako základní typ metody, ale jako reprezentaci modulu runtime tohoto typu použijete Regex objekt, jak ukazuje následující příklad.

  • Volání konstruktoru Regex vyvolá, ArgumentException když regulární výraz není platný. Kompilátor tuto výjimku zachytí a oznámí uživateli chybovou zprávu v době kompilace nebo v editoru sady Visual Studio. Tato výjimka umožňuje ověření regulárních výrazů bez spuštění aplikace.

Výše definovaný typ ještě není užitečný, protože neobsahuje žádné smysluplné metody nebo vlastnosti. Nejprve přidejte statickou IsMatch metodu:

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

Předchozí kód definuje metodu IsMatch, která přebírá řetězec jako vstup a vrací bool. Jedinou záludnou částí je použití argumentu args v definici InvokeCode . V tomto příkladu args je seznam uvozovek, které představují argumenty této metody. Pokud je metoda instance metoda, první argument představuje this argument. Pro statickou metodu jsou ale argumenty pouze explicitními argumenty metody. Všimněte si, že typ citované hodnoty by měl odpovídat zadanému návratovém typu (v tomto případě bool). Všimněte si také, že tento kód používá metodu AddXmlDoc k zajištění, že poskytnutá metoda má také užitečnou dokumentaci, kterou můžete poskytnout prostřednictvím IntelliSense.

Dále přidejte metodu Match instance. Tato metoda by však měla vrátit hodnotu zadaného Match typu, aby k skupinám bylo možné přistupovat silným typem. Proto nejprve deklarujete Match typ. Vzhledem k tomu, že tento typ závisí na vzoru zadaném jako statický argument, musí být tento typ vnořen do definice parametrizovaného typu:

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

ty.AddMember matchTy

Potom přidáte jednu vlastnost do typu Shoda pro každou skupinu. Za běhu je shoda reprezentována jako Match hodnota, takže uvozovka, která definuje vlastnost, musí použít Groups indexovanou vlastnost k získání příslušné skupiny.

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

Znovu si všimněte, že do poskytnuté vlastnosti přidáváte dokumentaci XML. Všimněte si také, že vlastnost lze přečíst, pokud GetterCode je k dispozici funkce, a vlastnost lze zapsat, pokud SetterCode je k dispozici funkce, takže výsledná vlastnost je jen pro čtení.

Teď můžete vytvořit metodu instance, která vrátí hodnotu tohoto Match typu:

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

Vzhledem k tomu, že vytváříte metodu instance, představuje RegexTyped instanci, args[0] na které se volá metoda, a args[1] je vstupním argumentem.

Nakonec zadejte konstruktor, aby bylo možné vytvořit instance zadaného typu.

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

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

ty.AddMember ctor

Konstruktor pouze vymaže vytvoření standardní instance .NET Regex, která je znovu boxována na objekt, protože obj je vymazání poskytnutého typu. Při této změně funguje ukázkové využití rozhraní API, které jste zadali dříve v tématu, podle očekávání. Následující kód je dokončený a konečný:

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

Klíčové lekce

Tato část vysvětluje, jak vytvořit zprostředkovatele typu, který pracuje s jeho statickými parametry. Zprostředkovatel zkontroluje statický parametr a poskytuje operace na základě jeho hodnoty.

Zprostředkovatel typu, který je podporován místními daty

Často můžete chtít, aby poskytovatelé typů zobrazovali rozhraní API na základě nejen statických parametrů, ale také informací z místních nebo vzdálených systémů. Tato část popisuje zprostředkovatele typů, kteří jsou založení na místních datech, jako jsou místní datové soubory.

Jednoduchý zprostředkovatel souborů CSV

Jako jednoduchý příklad zvažte zprostředkovatele typu pro přístup k vědeckým datům ve formátu CSV (Čárka Oddělená hodnota). V této části se předpokládá, že soubory CSV obsahují řádek záhlaví následovaný daty s plovoucí desetinou čárkou, jak ukazuje následující tabulka:

Vzdálenost (měřič) Čas (sekunda)
50,0 3.7
100.0 5.2
150.0 6.4

Tato část ukazuje, jak poskytnout typ, který můžete použít k získání řádků s Distance vlastností typu float<meter> a Time vlastnosti typu float<second>. Pro jednoduchost jsou provedeny následující předpoklady:

  • Názvy záhlaví jsou buď bez jednotek, nebo mají tvar "Název (jednotka)" a neobsahují čárky.

  • Jednotky jsou všechny jednotky SYSTEM International (SI) definované modulem FSharp.Data.UnitSystems.SI.UnitNames Module (F#).

  • Jednotky jsou všechny jednoduché (například měřič) místo složených (například měřič/sekunda).

  • Všechny sloupce obsahují data s plovoucí desetinou čárkou.

Úplnější poskytovatel by tato omezení uvolnil.

Dalším krokem je zvážit, jak by mělo rozhraní API vypadat. Mějme soubor info.csv s obsahem z předchozí tabulky (ve formátu odděleném čárkami). Uživatelé poskytovatele by měli být schopni napsat kód, který se podobá následujícímu příkladu:

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

V tomto případě by kompilátor měl tato volání převést na něco jako v následujícím příkladu:

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

Optimální překlad bude vyžadovat, aby poskytovatel typu definoval skutečný CsvFile typ v sestavení zprostředkovatele typu. Poskytovatelé typů často spoléhají na několik pomocných typů a metod pro zabalení důležité logiky. Vzhledem k tomu, že míry se vymažou za běhu, můžete jako float[] vymazat typ řádku. Kompilátor bude považovat různé sloupce za různé typy měr. Například první sloupec v našem příkladu obsahuje typ float<meter>a druhý má float<second>. Vymazání reprezentace však může zůstat poměrně jednoduché.

Následující kód ukazuje jádro implementace.

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

Všimněte si následujících bodů implementace:

  • Přetížené konstruktory umožňují čtení původního souboru nebo jednoho se stejným schématem. Tento vzor je běžný při zápisu zprostředkovatele typu pro místní nebo vzdálené zdroje dat a tento vzor umožňuje použití místního souboru jako šablony pro vzdálená data.

  • K překladu relativních názvů souborů můžete použít hodnotu TypeProviderConfig předanou konstruktoru zprostředkovatele typů.

  • Metodu AddDefinitionLocation můžete použít k definování umístění zadaných vlastností. Pokud tedy použijete Go To Definition zadanou vlastnost, soubor CSV se otevře v sadě Visual Studio.

  • Tento ProvidedMeasureBuilder typ můžete použít k vyhledání jednotek SI a k vygenerování příslušných float<_> typů.

Klíčové lekce

Tato část vysvětluje, jak vytvořit zprostředkovatele typů pro místní zdroj dat s jednoduchým schématem obsaženým v samotném zdroji dat.

Další přechod

Následující části obsahují návrhy pro další studium.

Podívejte se na kompilovaný kód pro vymazání typů.

Abyste získali představu o tom, jak použití zprostředkovatele typu odpovídá vygenerovanému kódu, podívejte se na následující funkci pomocí HelloWorldTypeProvider dříve v tomto tématu.

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

Tady je obrázek výsledného kódu dekompilovaného pomocí ildasm.exe:

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

} // end of class Module1

Jak ukazuje příklad, všechny zmínky o typu Type1 a InstanceProperty vlastnosti byly vymazány, takže pouze operace s zahrnutými typy modulu runtime.

Zásady návrhu a pojmenování pro poskytovatele typů

Při vytváření zprostředkovatelů typů dodržujte následující konvence.

Poskytovatelé pro protokoly Připojení ivity Obecně platí, že názvy většiny knihoven DLL poskytovatelů pro protokoly připojení dat a služeb, jako jsou připojení OData nebo SQL, by měly končit TypeProvider nebo TypeProviders. Použijte například název knihovny DLL, který se podobá následujícímu řetězci:

Fabrikam.Management.BasicTypeProviders.dll

Ujistěte se, že zadané typy jsou členy odpovídajícího oboru názvů, a uveďte protokol připojení, který jste implementovali:

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

Zprostředkovatelé nástrojů pro obecné kódování U zprostředkovatele typu nástroje, jako je například u regulárních výrazů, může být zprostředkovatel typu součástí základní knihovny, jak ukazuje následující příklad:

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

V tomto případě by se zadaný typ zobrazil v odpovídajícím bodě podle běžných konvencí návrhu .NET:

  open Fabrikam.Core.Text.RegexTyped

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

Zdroje dat Singleton. Někteří poskytovatelé typů se připojují k jednomu vyhrazenému zdroji dat a poskytují pouze data. V tomto případě byste měli vynechat příponu TypeProvider a použít normální konvence pro pojmenování .NET:

#r "Fabrikam.Data.Freebase.dll"

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

Další informace najdete v konvenci návrhu GetConnection popsané dále v tomto tématu.

Vzory návrhu pro zprostředkovatele typů

Následující části popisují vzory návrhu, které můžete použít při vytváření zprostředkovatelů typů.

Vzor návrhu Get Připojení ion

Většina zprostředkovatelů typů by měla být zapsána tak, aby používala GetConnection vzor používaný poskytovateli typů v FSharp.Data.TypeProviders.dll, jak ukazuje následující příklad:

#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

Poskytovatelé typů zajištění vzdálenými daty a službami

Než vytvoříte poskytovatele typu, který je založený na vzdálených datech a službách, musíte zvážit celou řadu problémů, které jsou součástí propojeného programování. Mezi tyto problémy patří následující aspekty:

  • Mapování schématu

  • liveness and invalidation in the presence of schema change

  • Ukládání schémat do mezipaměti

  • asynchronní implementace operací přístupu k datům

  • podpůrných dotazů, včetně dotazů LINQ

  • přihlašovací údaje a ověřování

Toto téma tyto problémy dále nezkoumá.

Další techniky vytváření

Při psaní vlastních poskytovatelů typů můžete chtít použít následující další techniky.

Vytváření typů a členů na vyžádání

Rozhraní Api ProvidedType má zpožděné verze AddMember.

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

Tyto verze se používají k vytváření prostorů typů na vyžádání.

Poskytování typů polí a vytváření instancí obecného typu

Poskytujete členy (jejichž podpisy zahrnují typy polí, byref typy a instance obecných typů) pomocí normálního MakeArrayTypea MakePointerTypeMakeGenericType libovolného výskytu Type, včetně ProvidedTypeDefinitions.

Poznámka:

V některých případech může být nutné použít pomocníka v ProvidedTypeBuilder.MakeGenericType. Další podrobnosti najdete v dokumentaci k sadě SDK zprostředkovatele typů.

Poskytování poznámek k měrné jednotce

Rozhraní ProvidedTypes API poskytuje pomocné rutiny pro poskytování poznámek k měrám. Pokud chcete například zadat typ float<kg>, použijte následující kód:

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

K zadání typu Nullable<decimal<kg/m^2>>použijte následující kód:

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

Přístup k místním prostředkům projektu nebo skriptu

Každá instance zprostředkovatele typu může být během výstavby poskytnuta TypeProviderConfig hodnota. Tato hodnota obsahuje složku překladu pro zprostředkovatele (tj. složku projektu pro kompilaci nebo adresář, který obsahuje skript), seznam odkazovaných sestavení a další informace.

Neplatnost

Poskytovatelé můžou vyvolat neplatné signály, které upozorní službu jazyka F#, že se mohly změnit předpoklady schématu. Pokud dojde k zneplatnění, provede se kontrola typů znovu, pokud je zprostředkovatel hostovaný v sadě Visual Studio. Tento signál se bude ignorovat, když je zprostředkovatel hostovaný v F# Interactive nebo kompilátorem F# (fsc.exe).

Informace o schématu Ukládání do mezipaměti

Zprostředkovatelé musí často ukládat přístup k informacím o schématu do mezipaměti. Data uložená v mezipaměti by měla být uložena pomocí názvu souboru, který je zadaný jako statický parametr nebo jako uživatelská data. Příkladem ukládání schématu do LocalSchemaFile mezipaměti je parametr ve zprostředkovatelích typů v FSharp.Data.TypeProviders sestavení. Při implementaci těchto zprostředkovatelů tento statický parametr směruje zprostředkovatele typu, aby místo přístupu ke zdroji dat přes síť používal informace o schématu v zadaném místním souboru. Pokud chcete použít informace o schématu uloženém v mezipaměti, musíte také nastavit statický parametr ForceUpdate na falsehodnotu . K povolení online a offline přístupu k datům můžete použít podobnou techniku.

Záložní sestavení

Při kompilaci .dll nebo .exe souboru je záložní .dll soubor pro vygenerované typy staticky propojený s výsledným sestavením. Tento odkaz je vytvořen zkopírováním definic typů zprostředkujícího jazyka (IL) a všech spravovaných prostředků ze záložního sestavení do konečného sestavení. Při použití F# Interactive se záložní .dll soubor nekopíruje a místo toho se načte přímo do procesu F# Interactive.

Výjimky a diagnostika z poskytovatelů typů

Všechna použití všech členů z zadaných typů mohou vyvolat výjimky. Ve všech případech, pokud poskytovatel typu vyvolá výjimku, kompilátor hostitele přiřadí chybu konkrétnímu poskytovateli typu.

  • Výjimky zprostředkovatele typů by nikdy neměly vést k interním chybám kompilátoru.

  • Zprostředkovatelé typů nemůžou hlásit upozornění.

  • Pokud je poskytovatel typu hostovaný v kompilátoru F#, vývojovém prostředí F# nebo F# Interactive, jsou zachyceny všechny výjimky z daného poskytovatele. Vlastnost Message je vždy text chyby a nezobrazí se žádné trasování zásobníku. Pokud vyvoláte výjimku, můžete vyvolat následující příklady: System.NotSupportedException, System.IO.IOException, System.Exception.

Poskytování vygenerovaných typů

Zatím tento dokument vysvětluje, jak poskytnout odstraněné typy. Pomocí mechanismu zprostředkovatele typů v jazyce F# můžete také poskytnout generované typy, které se přidají jako skutečné definice typů .NET do programu uživatelů. Je nutné odkazovat na vygenerované typy pomocí definice typu.

open Microsoft.FSharp.TypeProviders

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

Pomocný kód ProvidedTypes-0.2, který je součástí verze F# 3.0, má pouze omezenou podporu pro poskytování vygenerovaných typů. Následující příkazy musí být pravdivé pro vygenerovanou definici typu:

  • isErased musí být nastavena na falsehodnotu .

  • Vygenerovaný typ musí být přidán do nově vytvořeného ProvidedAssembly()objektu , který představuje kontejner pro vygenerované fragmenty kódu.

  • Zprostředkovatel musí mít sestavení, které má skutečný záložní soubor .NET .dll s odpovídajícím souborem .dll na disku.

Pravidla a omezení

Při psaní poskytovatelů typů mějte na paměti následující pravidla a omezení.

Zadané typy musí být dosažitelné.

Všechny zadané typy by měly být dostupné z nenořených typů. Typy, které nejsou vnořené, jsou uvedeny ve volání konstruktoru TypeProviderForNamespaces nebo volání AddNamespace. Pokud například poskytovatel poskytuje typ StaticClass.P : T, musíte zajistit, aby T byl buď nenořený typ, nebo vnořený pod jedním.

Někteří poskytovatelé mají například statickou třídu, jako DataTypes je ta, která tyto T1, T2, T3, ... typy obsahuje. V opačném případě chyba říká, že byl nalezen odkaz na typ T v sestavení A, ale typ nebyl nalezen v daném sestavení. Pokud se zobrazí tato chyba, ověřte, že všechny podtypy jsou dostupné z typů zprostředkovatelů. Poznámka: Tyto T1, T2, T3... typy se označují jako typy za běhu . Nezapomeňte je umístit do přístupného oboru názvů nebo nadřazeného typu.

Omezení mechanismu poskytovatele typů

Mechanismus poskytovatele typů v jazyce F# má následující omezení:

  • Základní infrastruktura pro poskytovatele typů v jazyce F# nepodporuje obecné typy ani poskytnuté obecné metody.

  • Mechanismus nepodporuje vnořené typy se statickými parametry.

Tipy pro vývoj

Během procesu vývoje můžou být užitečné následující tipy:

Spuštění dvou instancí sady Visual Studio

Poskytovatele typu můžete vyvíjet v jedné instanci a otestovat poskytovatele v druhé, protože testovací integrované vývojové prostředí (IDE) převezme zámek v souboru .dll, který brání tomu, aby se zprostředkovatel typu znovu sestavil. Proto je nutné zavřít druhou instanci sady Visual Studio, zatímco je zprostředkovatel sestaven v první instanci, a potom je nutné znovu otevřít druhou instanci po sestavení zprostředkovatele.

Ladění zprostředkovatelů typů pomocí vyvolání fsc.exe

Poskytovatele typů můžete vyvolat pomocí následujících nástrojů:

  • fsc.exe (kompilátor příkazového řádku F#)

  • fsi.exe (kompilátor F# Interactive)

  • devenv.exe (Visual Studio)

Zprostředkovatelé typů můžete často ladit nejčastěji pomocí fsc.exe v souboru testovacího skriptu (například script.fsx). Ladicí program můžete spustit z příkazového řádku.

devenv /debugexe fsc.exe script.fsx

Protokolování tisk-to-stdout můžete použít.

Viz také