Partager via


Didacticiel : création d'un fournisseur de type (F#)

Le mécanisme type de fournisseur dans F# 3.0 est une partie importante de son support pour la programmation riche d'informations. Ce tutoriel explique comment créer vos propres fournisseurs de type et vous guide dans le développement de plusieurs fournisseurs de type simples pour illustrer les concepts de base. Pour plus d'informations sur le type mécanisme de fournisseur dans F#, consultez Fournisseurs de type.

F# 3.0 contient plusieurs fournisseurs de type intégrés pour les services de données d'Internet et d'entreprise couramment utilisés. Ces types fournisseurs offrent l'accès simple et normal aux bases de données relationnelles SQL et services réseau de type OData et WSDL. Ces fournisseurs prennent également en charge l'utilisation des requêtes LINQ F# sur ces sources de données.

En cas de besoin, vous pouvez créer votre propre fournisseur de type personnalisé, ou référencer des fournisseurs de type qui ont été créés par d'autres. Par exemple, supposez que votre organisation a un service de données fournissant un grand nombre de plus en plus important de groupes de données nommés, chacun avec son propre schéma stable de données. Vous pouvez créer un type de fournisseur qui lit les schémas et répertorie les groupes de données actuels au programmeur d'une méthode fortement typée.

Avant de commencer

Le type mécanisme de fournisseur est principalement conçu pour injecter des espaces stables de données et d'information dans l'expérience de programmation F#.

Ce mécanisme n'est pas conçu pour injecter des espaces d'informations dont les modifications de schéma pendant l'exécution du programme des méthodes appropriées à la logique de programme. En outre, le mécanisme n'est pas destiné à une méta programmation intra- langage, même si ce domaine contient des utilisations valides. Vous devez utiliser ce mécanisme uniquement en cas de besoin et où le développement d'un type référence fournisseur aura une valeur élevée.

Vous devez éviter d'écrire un type fournisseur où un schéma est pas disponible. De même, vous devez éviter d'écrire un type fournisseur où une bibliothèque ordinaire (voire une existant) .NET suffirait.

Avant de commencer, vous pouvez poser des questions suivantes :

  • Avez -vous un schéma de pour votre source d'informations ? Si oui, qui est le mappage dans le système de type F# et .NET ?

  • Pouvez -vous utiliser une API (dynamiquement typée) existante comme point de départ pour votre implémentation ?

  • Vous et votre organisation avez suffisamment d'utilisation du type fournisseur pour le rendre utile ? Est-ce-qu'une bibliothèque normale .NET répondrait à vos besoins ?

  • Le nombre votre modification du schéma ?

  • Changera-t-il lors du codage ?

  • Changera-t-il entre les sessions de codage ?

  • Changera-t-il pendant l'exécution du programme ?

Le type fournisseurs est le mieux adaptée aux situations dans les cas où le schéma est stable au moment de l'exécution et pendant la durée de vie du code compilé.

Un type simple fournisseur

Cet exemple est activé Samples.HelloWorldTypeProvider dans le répertoire SampleProviders\Providers de pack exemple F# 3,0 du site Web Codeplex. Le fournisseur est disponible un « type l'espace » qui contient 100 types supprimés, comme l'illustre le code suivant à l'aide de la syntaxe de signature F# et d'omettre les informations pour tous sauf Type1. Pour plus d'informations sur les types supprimés, consultez Détails sur les types fournis supprimés plus loin dans cette rubrique.

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

        /// This is an instance property.
        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 =
        …

Notez que l'ensemble de types et de membres fournis est statiquement connu. Cet exemple ne tire parti pas la capacité des fournisseurs de fournir les types qui dépendent d'un schéma. L'implémentation du type fournisseur est décrite dans le code suivant, et les détails sont traités dans les sections suivantes de cette rubrique.

Avertissement

Il peut y avoir de petites différences d'affectation de noms entre ce code et les exemples en ligne.

namespace Samples.FSharp.HelloWorldTypeProvider

open System
open System.Reflection
open Samples.FSharp.ProvidedTypes
open Microsoft.FSharp.Core.CompilerServices
open Microsoft.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()

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

Pour utiliser ce fournisseur, ouvrez une instance séparée de Visual Studio 2012, créez un script F#, puis ajoutez une référence au fournisseur de votre script à l'aide de #r comme le code suivant :

#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

Recherchez ensuite les types dans l'espace de noms Samples.HelloWorldTypeProvider que le type fournisseur a généré.

Avant de recompiliez le fournisseur, assurez-vous que vous avez fermé toutes les instances de Visual Studio et F# qui utilisent la DLL de fournisseur. Sinon, une erreur de build se produit parce que la DLL de sortie est verrouillé.

Pour déboguer ce fournisseur à l'aide des instructions d'impression, créez un script qui expose un problème avec le fournisseur, puis utilisez le code suivant :

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

Pour déboguer ce fournisseur à l'aide de Visual Studio, ouvrez l'invite de commandes de Visual Studio avec les informations d'identification d'administration, puis exécutez la commande suivante :

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

Alternativement, ouvrez Visual Studio, ouvrez le menu Déboguer, choisissez Déboguer/attacher au processus…, et attachez un autre processus devenv pour modifier votre script. En utilisant cette méthode, vous pouvez plus facilement cibler une logique particulière dans le type de fournisseur par taper des expressions interactivement dans la deuxième instance (avec IntelliSense complète et autres fonctionnalités).

Vous pouvez désactiver uniquement mon débogage de code pour mieux identifier des erreurs dans le code généré. Pour plus d'informations sur la façon d'activer ou de désactiver cette fonctionnalité, consultez Effectuer un pas à pas détaillé pour Uniquement mon code. En outre, vous pouvez également définir l'interception d'exceptions de première chance en ouvrant le menu Déboguer puis choisissez Exceptions ou en choisissant Ctrl+Alt+E les clés pour ouvrir la boîte de dialogue Exceptions. Dans cette boîte de dialogue, sous Exceptions du Common Language Runtime, activez la case à cocher Levé.

Implémentation du fournisseur.

Cette section vous présente les principales sections du type d'implémentation de fournisseur. D'abord, vous définissez le type pour le type personnalisé fournisseur lui-même :

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

Ce type doit être publique, et vous devez le marquer avec l'attribut de TypeProvider afin que le compilateur identifie le type de fournisseur lorsque les références de projet distinctes d'un F# l'assembly qui contient le type. Le paramètre d' config est facultatif, et, le cas échéant, contient des données de configuration contextuelles pour le type instance de fournisseur que le compilateur F# crée.

Ensuite, vous implémentez l'interface d' ITypeProvider. Dans ce cas, vous utilisez le type d' TypeProviderForNamespaces de l'API de ProvidedTypes comme type de base. Ce type d'assistance peut fournir une collection finie des espaces de noms ardemment fournis, qui contient directement un nombre terminé de fixe, les types ardemment fournis. Dans ce contexte, le fournisseur génère ardemment des types même s'ils ne sont pas nécessaires ou ne sont pas utilisés.

inherit TypeProviderForNamespaces()

Ensuite, définissez les valeurs privées locales qui spécifient l'espace de noms pour les types fournis, et recherchez le type l'assembly fournisseur lui-même. Cet assembly est utilisé ultérieurement comme type parent logique des types supprimés fournis.

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

Ensuite, créez une fonction pour fournir ces types tapez 1… Type100. Cette fonction est expliquée plus en détail ultérieurement dans cette rubrique.

let makeOneProvidedType (n:int) = …

Ensuite, générez les 100 types fournis :

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

Ensuite, ajoutez les types comme espace de noms fourni :

do this.AddNamespace(namespaceName, types)

Enfin, ajoutez un attribut d'assembly qui indique que vous créez un type DLL de fournisseur :

[<assembly:TypeProviderAssembly>] 
do()

Fourniture d'un type et ses membres

La fonction d' makeOneProvidedType fait le vrai travail de fournir un des types.

let makeOneProvidedType (n:int) = 
        …

Cette étape illustre l'implémentation de cette fonction. En premier lieu, créez le type fourni (par exemple, tapez 1, lorsque n = 1, ou Type57, lorsque 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>)

Vous devez noter les points suivants :

  • Cela a fourni le type est supprimé. Étant donné que vous indiquez que le type de base est obj, les instances apparaissent comme des valeurs de type obj dans le code compilé.

  • Lorsque vous spécifiez un type non imbriqué, vous devez spécifier l'assembly et l'espace de noms. Pour les types supprimés, l'assembly doit être le type l'assembly fournisseur lui-même.

Ensuite, ajoutez la documentation XML au type. Cette documentation est différée, autrement dit., a calculé à la demande si le compilateur hôte a besoin.

t.AddXmlDocDelayed (fun () -> sprintf "This provided type %s" ("Type" + string n))

Ensuite vous ajoutez une propriété statique fournie au type :

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

Obtention de cette propriété évalue toujours à la chaîne « hello ! ». Le GetterCode pour la propriété utilise une quotation F#, qui représente le code que le compilateur génère hôte pour obtenir la propriété. Pour plus d'informations sur les quotations, consultez Quotations de code (F#).

Ajoutez une documentation XML à la propriété.

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

Attachez maintenant la propriété fournie au type fourni. Vous devez joindre un membre fourni à un seul type. Sinon, le membre ne sera jamais accessible.

t.AddMember staticProp

Créez maintenant un constructeur fourni qui n'accepte pas de paramètre.

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

Le InvokeCode pour le constructeur retourne une quotation F#, qui représente le code que le compilateur génère l'hôte lorsque le constructeur est appelé. Par exemple, vous pouvez utiliser constructeur suivant :

new Type10()

Une instance du type fourni est créée avec les données sous-jacentes « des données d'objet ». Le code entre guillemets inclut une conversion vers obj car ce type est l'effacement de ce type fourni (comme vous avez spécifiée lorsque vous avez déclaré le type fourni).

Ajoutez la documentation XML au constructeur, et ajoutez le constructeur fourni au type fourni :

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

t.AddMember ctor

Créez un second constructeur qui prend un paramètre :

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

Le InvokeCode pour le constructeur retourne à nouveau une quotation F#, qui représente le code que le compilateur hôte créé pour un appel à la méthode. Par exemple, vous pouvez utiliser constructeur suivant :

     new Type10("ten")

Une instance du type fourni est créée avec les données sous-jacentes « dix ». Vous avez peut-être déjà remarqué que la fonction d' InvokeCode retourne une quotation. L'entrée à cette fonction est une expression, une pour chaque paramètre de constructeur. Dans ce cas, une expression qui représente la valeur du paramètre unique est disponible dans args.[0]. Le code d'un appel au constructeur force la valeur de retour en type supprimé obj. Après avoir ajouté le deuxième avez fourni le constructeur au type, vous avez créé une propriété fournie par l'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

Obtention de cette propriété retourne la longueur de la chaîne, qui est la représentation de l'objet. La propriété d' GetterCode retourne une quotation F# qui spécifie le code que le compilateur hôte se produit pour accéder à la propriété. Comme InvokeCode, la fonction d' GetterCode retourne une quotation. Le compilateur hôte appelle cette fonction avec une liste d'arguments. Dans ce cas, les arguments sont simplement l'expression unique qui représente l'instance sur laquelle l'accesseur Get est appelé, auquel vous pouvez accéder à l'aide de args.[0]. L'implémentation des épissures d' GetterCode puis dans la quotation de résultat au type supprimé obj, et un cast est utilisée pour satisfaire le mécanisme du compilateur pour le contrôle des types que l'objet est une chaîne. La partie suivante d' makeOneProvidedType fournit une méthode d'instance un paramètre.

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 

Enfin, créez un type imbriqué qui contient 100 propriétés imbriquées. La création de ce problème a le type imbriqué et ses propriétés sont retardées, autrement dit, calculées à la demande.

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

)

    nestedType.AddMembersDelayed (fun () -> 
        let staticPropsInNestedType = 
            [ for i in 1 .. 100 do
                 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 () -> 
                     sprintf "This is StaticProperty%d on NestedType" i)

                 yield p ]
        staticPropsInNestedType)

    [nestedType])

// The result of makeOneProvidedType is the type.
t

Détails sur les types fournis supprimés

L'exemple fournit de cette section uniquement des types fournis supprimés, qui sont particulièrement utiles dans les situations suivantes :

  • Lorsque vous écrivez un fournisseur pour un espace d'informations qui contient uniquement des données et des méthodes.

  • Lorsque vous écrivez un fournisseur précis où sémantique de type au moment de l'exécution n'êtes pas importante pour une utilisation pratique de l'espace d'informations.

  • Lorsque vous écrivez un fournisseur pour un espace d'informations qui est si grand et interconnecté qu'il ne soit pas techniquement possible de générer de vrais type. NETS de l'espace d'informations.

Dans cet exemple, chaque a fourni le type est supprimé en type obj, et toutes les utilisations de type apparaîtront en tant que type obj dans le code compilé. En fait, les objets sous-jacents dans ces exemples sont des chaînes, mais le type apparaît comme Object dans le code compilé par .NET. Comme avec toutes les utilisations de type effacement, vous pouvez utiliser la conversion boxing, une conversion unboxing, et le cast explicite pour renverser les types supprimés. Dans ce cas, une exception de cast non valide peut survenir lorsque l'objet est utilisé. Un runtime de fournisseur peut définir son propre type privé de performance pour vous protéger les représentations false. Vous ne pouvez pas définir de types supprimé dans F# lui-même. Seuls les types fournis peuvent être supprimés. Vous devez comprendre les ramifications, pratiques et sémantique, d'utiliser des types supprimés pour votre type fournisseur ou un fournisseur qui contribue a supprimé des types. Un type supprimé n'a aucun type réel. NET. Par conséquent, vous ne pouvez pas utiliser la réflexion précis sur le type, et vous pouvez renverser les types supprimés si vous utilisez des casts de exécution et d'autres techniques qui reposent sur la sémantique exacte de type au moment de l'exécution. La subversion des types supprimés a souvent provoqué des exceptions de cast de type au moment de l'exécution.

Choisir les représentations des types fournis supprimés

Pour certaines fonctionnalités des types fournis supprimés, aucune représentation n'est requise. Par exemple, le type fourni supprimé peut contenir uniquement les propriétés statiques et les membres et les constructeurs, et aucune méthode ou propriété ne retourne une instance du type. Si vous pouvez accomplir des instances d'un type fourni supprimé, vous devez considérer les points suivants :

  • Quel est l'effacement d'un type fourni ?

    • L'effacement d'un type fourni est comment le type figure dans le code compilé .NET.

    • L'effacement d'un type supprimé fourni de classe est toujours le premier de base non supprimé dans la chaîne d'héritage de type.

    • L'effacement d'un type d'interface supprimé fourni est toujours Object.

  • Les sont des représentations d'un type fourni ?

    • L'ensemble d'objets possibles pour un type fourni supprimé sont appelés les représentations. Dans l'exemple de ce document, les représentations de tous les types fournis supprimés Type1..Type100 sont toujours des objets String.

Toutes les représentations d'un type fourni doivent être compatibles avec l'effacement du type fourni. (Sinon, ou le compilateur F# donnera une erreur pour un usage du type fournisseur, ou le code non vérifiable .NET qui n'est pas valide est généré. Un fournisseur de type n'est pas valide s'il renvoie du code qui donne une représentation invalide.

Vous pouvez choisir une représentation pour les objets fournis à l'aide de l'une des approches suivantes, qui sont très courantes :

  • Si vous indiquez simplement un wrapper fortement typé sur un type existant. NET souvent, il est logique de votre type s'efface à ce type, utilise des instances de ce type comme représentations, ou les deux. Cette approche est appropriée lorsque la plupart des méthodes existantes sur ce type s'affichent toujours raisonnable lorsque vous utilisez la version fortement typée.

  • Si vous souhaitez créer une API qui diffère considérablement d'une API existante .NET, il est logique de créer des types au moment de l'exécution qui seront le type effacement et représentations des types fournis.

l'exemple dans ce document utilise des chaînes comme représentations des objets fournis. Souvent, il peut s'avérer nécessaire d'utiliser d'autres objets pour les représentations. Par exemple, vous pouvez utiliser un dictionnaire comme conteneur des propriétés :

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

Sinon, vous pouvez définir un type dans votre type de fournisseur qui sera utilisé au moment de l'exécution pour former les performances, avec un ou plusieurs opérations d'exécution :

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

Si les membres peuvent ensuite construire des instances avec de ce type d'objet :

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

Dans ce cas, vous pouvez (facultatif) utiliser ce type comme type effacement en spécifiant ce type comme baseType en construisant ProvidedTypeDefinition :

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

Leçons principales

La section précédente a montré comment créer un type en supprimant simple fournisseur qui fournit une plage de types, les propriétés, les méthodes et. Cette section présente également l'illustre le concept de type effacement, notamment certains des avantages et des inconvénients de fournir les types supprimés d'un type fournisseur, et les représentations abordées des types supprimés.

Un type fournisseur qui utilise des paramètres statiques

La capacité de paramétrer le type fournisseurs par les données statiques permet de nombreux scénarios intéressants, même dans les cas où le fournisseur n'a pas besoin d'un accès à aucune donnée locale ou distante. Dans cette section, vous apprendrez des techniques de base pour réunir ce fournisseur.

Tapez le fournisseur contrôlé d'expression régulière

Imaginez que vous souhaitez implémenter un fournisseur type pour les expressions régulières qui encapsule les bibliothèques d' Regex .NET dans une interface qui fournit les garanties de compilation suivantes :

  • Vérifier si une expression régulière est valide.

  • Fournissant les propriétés nommées sur les correspondances basés sur tous les noms de groupes dans l'expression régulière.

Cette section indique comment utiliser le type fournisseurs pour créer un type d' RegExProviderType que le modèle d'expression régulière configure pour fournir ces avantages. Le compilateur signale une erreur si le modèle fourni est pas valide, et le type fournisseur peut extraire des groupes du modèle afin de pouvoir y accéder en utilisant les propriétés nommées sur les correspondances. Lorsque vous concevez un type de fournisseur, vous devez déterminer comment son API exposée la doit ressembler aux utilisateurs finaux et comment cette conception traduira au .NET le code. L'exemple suivant montre comment utiliser une telle API pour obtenir les composants de code postal :

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"

L'exemple suivant montre comment le type fournisseur convertit ces appels :

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"

Notez les points suivants :

  • Le type standard d'expression régulière représente le type paramétrable d' RegexTyped.

  • Le constructeur d' RegexTyped entraîne un appel au constructeur d'expression régulière, en passant l'argument statique du modèle.

  • Les résultats de la méthode d' Match sont représentés par le type standard d' Match.

  • Chaque groupe nommé trouve une propriété fournie, et l'accès de la propriété entraîne l'utilisation d'un indexeur sur la collection d' Groups d'une correspondance.

Le code suivant est le cœur de la logique pour implémenter ce fournisseur, et cet exemple omet l'ajout de tous les membres au type fourni. Pour plus d'informations sur chaque membre ajouté, consultez la section appropriée plus loin dans cette rubrique. Pour obtenir le code complet, téléchargez l'exemple dans en-tête pack exemple F# 3,0 sur le site Web Codeplex.

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

Notez les points suivants :

  • Le type de fournisseur prend deux paramètres statiques : pattern, qui est obligatoire, et options, qui sont tous optionnels (car une valeur par défaut est fournie).

  • Une fois les arguments statiques sont fournis, vous créez une instance de l'expression régulière. Cette instance lève une exception si l'expression régulière est incorrecte, et cette erreur est signalée aux utilisateurs.

  • Dans le rappel d' DefineStaticParameters, vous définissez le type qui est retourné après que les arguments sont fournis.

  • Ce code définit HideObjectMethods la valeur true afin que l'expérience IntelliSense reste profilée. Cet attribut provoque Equals, GetHashCode, Finalize, et les membres d' GetType à supprimer des listes d'IntelliSense pour un objet fourni.

  • Vous utilisez obj comme type de base de la méthode, mais vous utiliserez un objet d' Regex comme une représentation d'exécution de ce type, comme l'exemple suivant illustre.

  • L'appel au constructeur d' Regex lève ArgumentException lorsqu'une expression régulière est pas valide. Le compilateur intercepte l'exception et enregistre un message d'erreur à l'utilisateur au moment de la compilation ou dans l'éditeur Visual Studio. Cette exception autorise des expressions régulières d'être validées sans exécuter une application.

Le type défini ci-dessus n'est pas utile encore car il ne contient aucune méthode ou propriété explicites. Tout d'abord, ajoutez une méthode statique d' IsMatch:

let isMatch = ProvidedMethod(
                methodName = "IsMatch", 
                parameters = [ProvidedParameter("input", typeof<string>)], 
                returnType = typeof<bool>, 
                IsStaticMethod = 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

Le code précédent définit une méthode IsMatch, qui prend une chaîne comme entrée et retourne bool. La seule partie délicate est l'utilisation de l'argument d' args dans la définition d' InvokeCode. Dans cet exemple, args est un bulletin d'exécution qui représente les arguments de cette méthode. Si la méthode est une méthode d'instance, le premier argument représente l'argument d' this. Toutefois, pour une méthode statique, tous les arguments sont simplement des arguments explicites à la méthode. Notez que le type de la valeur entre guillemets doit correspondre au type de retour spécifié (dans ce cas, bool). Notez également que ce code utilise la méthode de AddXmlDoc pour vous assurer que la méthode fournie a également la documentation utile, que vous pouvez fournir via IntelliSense.

Ensuite, ajoutez une méthode de correspondance d'instance. Toutefois, cette méthode doit retourner la valeur d'un type fourni d' Match afin que les groupes puissent accéder de manière fortement typée. Ainsi, vous déclarez d'abord le type d' Match. Comme ce type dépend du modèle fourni comme argument statique, ce type doit être imbriqué dans la définition de type paramétré :

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

ty.AddMember matchTy

Vous ajoutez ensuite une propriété en type de correspondance pour chaque groupe. Au moment de l'exécution, une correspondance est représentée comme valeur d' Match, la quotation qui définit la propriété doit utiliser la propriété indexée par Groups pour obtenir le groupe approprié.

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

Là encore, notez que vous ajoutez la documentation XML à la propriété fournie. Notez également qu'une propriété peut être lue si une fonction d' GetterCode est fournie, et la propriété peut être écrite si une fonction d' SetterCode est fournie, la propriété obtenue est en lecture seule.

Vous pouvez à présent créer une méthode d'instance qui retourne une valeur de ce type d' Match:

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

Étant donné que vous créez une méthode d'instance, args.[0] représente l'instance d' RegexTyped sur lequel la méthode est appelée, et args.[1] est l'argument d'entrée.

Enfin, fournissez un constructeur afin que les instances du type fourni puissent être créées.

let ctor = ProvidedConstructor(
            parameters = [], 
            InvokeCode = fun args -> <@@ Regex(pattern, options) :> obj @@>)
ctor.AddXmlDoc("Initializes a regular expression instance.")

ty.AddMember ctor

Les effacements de constructeur seulement à la création d'une expression régulière standard .NET instance, qui est encore enfermée dans une boîte à un objet car obj est l'effacement du type fourni. Avec cette modification, l'utilisation d'API d'exemple qui a spécifié précédemment dans la rubrique fonctionne comme prévu. Le code suivant est terminé et final :

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>, 
                            IsStaticMethod = 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 occurence 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 ()

Leçons principales

Cette section a montré comment créer un type de fournisseur qui traite ses paramètres statiques. Le fournisseur contrôle le paramètre statique et fournit des opérations selon sa valeur.

Un type fournisseur stocké par les données locales

Fréquemment vous pouvez le type fournisseurs pour présenter des API fonction non seulement des paramètres statiques et des informations sur les systèmes locaux ou distants. Cette section décrit le type fournisseurs qui sont basés sur les données locales, telles que les fichiers de données locaux.

Fournisseur simple de fichier CSV

À titre de exemple simple, considérez un type fournisseur pour accéder aux données scientifiques dans le format de (CSV) de valeurs séparées par des virgules. Cette section suppose que les fichiers CSV contiennent une ligne d'en-tête suivie des données à virgule flottante, comme indiqué dans le tableau suivant :

Distance (mètre)

Délai (secondes)

50.0

3.7

100.0

5.2

150.0

6.4

Cette section indique comment fournir un type que vous pouvez utiliser pour obtenir des lignes à une propriété d' Distance de type float<meter> et une propriété d' Time de type float<second>. Pour plus de simplicité, les hypothèses suivantes sont effectuées :

  • Les noms d'en-tête sont sans unité ou ont la forme « nom (unité) » et ne contiennent pas des virgules.

  • Les unités sont les unités internationales de Systeme (SI) définies par le module Microsoft.FSharp.Data.UnitSystems.SI.UnitNames (F#).

  • Les unités sont toutes simples (par exemple, mètre) plutôt que le composite (par exemple, mètre/seconde).

  • Toutes les colonnes contiennent des données à virgule flottante.

Un fournisseur plus complet détacherait ces restrictions.

De nouveau la première étape consiste à déterminer comment l'API doit la surveiller. Étant donné un fichier info.csv avec le contenu du tableau précédent (au format séparé par des virgules), les utilisateurs du fournisseur devraient pouvoir écrire un code similaire à l'exemple suivant :

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

Dans ce cas, le compilateur doit convertir ces appels dans présenter comme dans l'exemple suivant :

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

La traduction optimale nécessite du type fournisseur de définir la valeur true CsvFile dans l'assembly de type du fournisseur. Le type fournisseurs reposent généralement sur certains types et méthodes d'assistance pour inclure la logique importante. Les mesures sont supprimées au moment de l'exécution, vous pouvez utiliser float[] lorsque le type supprimé d'une ligne. Le compilateur traitera les colonnes comme ayant la mesure de types différente. Par exemple, la première colonne dans notre exemple a le type float<meter>, et la deuxième a float<second>. Toutefois, la représentation supprimée peut restent assez simple.

L'exemple de code suivant montre l'implémentation du core.

// 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 do
                yield line.Split(',') |> Array.map float }
        |> Seq.cache
    member __.Data = data

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

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

Notez les points suivants à propos de l'implémentation :

  • Les constructeurs surchargés permettent le fichier d'origine ou un qui ont un schéma identique à lire. Ce modèle est courant lorsque vous écrivez un type fournisseur pour les sources de données locales et distantes, et ce modèle permet à un fichier local à utiliser comme modèle pour les données distantes.

    Vous pouvez utiliser la valeur de TypeProviderConfig qui est passée au constructeur de type de fournisseur pour résoudre les noms de fichiers associés.

  • Vous pouvez utiliser la méthode d' AddDefinitionLocation pour définir l'emplacement des propriétés fournies. Par conséquent, si vous utilisez Atteindre la définition sur une propriété fournie, le fichier CSV s'ouvre dans Visual Studio.

  • Vous pouvez utiliser le type d' ProvidedMeasureBuilder pour rechercher les unités de SI et générer les types appropriés pour float<_>.

Leçons principales

Cette section a montré comment créer un type de fournisseur pour une source de données locale avec un schéma simple qui est contenu dans la source de données elle-même.

Aller plus loin

Les sections suivantes incluent des suggestions pour plus d'étude.

Un aspect du code compilé pour les types supprimés

Pour vous donner une idée de la façon dont l'utilisation du type fournisseur correspond au code émis, regardez à la fonction suivante à l'aide de HelloWorldTypeProvider utilisée précédemment dans cette rubrique.

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

Voici une image du code résultant decompiled à l'aide de 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

Comme le montre l'exemple indique, toutes les mentions du type Type1 et de la propriété d' InstanceProperty ont été supprimées, laissant ainsi que des opérations sur les types au moment de l'exécution impliquées.

Conception et conventions d'affectation de noms pour le type fournisseurs

Observez les conventions suivantes en créant le type fournisseurs.

  • Fournisseurs pour les protocoles de connectivité

    En général les noms de la plupart des DLL de fournisseur pour les protocoles de connectivité de données et de service, tels que ou OData connexions SQL, doivent se terminer dans TypeProvider ou TypeProviders. Par exemple, utilisez le nom de la DLL qui ressemble à la chaîne suivante :

    Fabrikam.Management.BasicTypeProviders.dll
    

    Vérifiez que vos types fournis sont des membres de l'espace de noms correspondant, et indiquez le protocole de connectivité que vous avez implémenté :

    Fabrikam.Management.BasicTypeProviders.WmiConnection<…>
    Fabrikam.Management.BasicTypeProviders.DataProtocolConnection<…>
    
  • Fournisseurs de service pour l'encodage général

    Pour un type de service fournisseur tel que pour les expressions régulières, le type fournisseur peut faire partie d'une bibliothèque de base, comme le montre l'exemple suivant :

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

    Dans ce cas, le type fourni apparaîtrait à un point approprié selon les conventions normales de conception du .NET :

    open Fabrikam.Core.Text.RegexTyped
    
    let regex = new RegexTyped<"a+b+a+b+">()
    
  • Sources de données de singleton

    Un certain type fournisseurs se connectent à une source de données de fonctionnalité unique et fournissent uniquement des données. Dans ce cas, vous devez supprimer le suffixe d' TypeProvider et utiliser les conventions normales pour nommer .NET :

    #r "Fabrikam.Data.Freebase.dll"
    
    let data = Fabrikam.Data.Freebase.Astronomy.Asteroids
    

    Pour plus d'informations, consultez la convention de conception d' GetConnection décrite plus loin dans cette rubrique.

Modèles de design pour le type fournisseurs

Les sections suivantes décrivent les modèles de conception que vous pouvez utiliser lors de la création du type fournisseurs.

Le modèle de design GetConnection

La plupart de type fournisseurs doit être écrit pour utiliser le modèle GetConnection utilisé par le type fournisseurs dans FSharp.Data.TypeProviders.dll, comme indiqué dans l'exemple suivant :

#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

Tapez les fournisseurs stockés par des données distantes et services

Avant de créer un type de fournisseur stocké par des données distantes et des services, vous devez considérer une plage des problèmes qui sont inhérentes à la programmation connectée. Ces questions incluent les considérations suivantes :

  • Schéma de mappage

  • liveness invalidation et en présence de modification de schéma

  • mise en cache de schémas

  • implémentations asynchrones des opérations d'accès aux données

  • requêtes de prise en charge, y compris les requêtes LINQ

  • Informations d'identification pour l'authentification.

Cette rubrique ne explore pas ces problèmes davantage.

Techniques supplémentaires de création

Lorsque vous écrivez votre propre type fournisseurs, vous pouvez utiliser les techniques supplémentaires suivantes.

  • Création des types et des membres à la demande

    L'API de ProvidedType a différé des versions d'AddMember.

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

    Ces versions sont utilisées pour créer les espaces à la demande de types.

  • En fournissant le tableau, de ByRef, et les types pointeur

    Vous définissez les membres fournis (dont les signatures incluent les types de tableau, les types byref, et les instanciations de types génériques) à l'aide de MakeArrayTypenormal, d' MakePointerType, et d' MakeGenericType sur toute instance System.Type, y compris ProvidedTypeDefinitions.

  • Fourniture de l'unité de mesure annotations

    L'API de ProvidedTypes fournit des programmes d'assistance pour fournir la mesure d'annotations. L'exemple de code suivant montre comment utiliser le type float<kg>.

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

    Pour fournir le type Nullable<decimal<kg/m^2>>, utilisez le code suivant :

    let kgpm2 = measures.Ratio(kg, measures.Square m)
    let dkgpm2 = measures.AnnotateType(typeof<decimal>,[kgpm2])
    let nullableDecimal_kgpm2 = typedefof<System.Nullable<_>>.MakeGenericType [|dkgpm2 |]
    
  • Projet local ou accéder aux ressources en Script- local

    Chaque instance d'un type fournisseur peut être attribuée une valeur d' TypeProviderConfig pendant la construction. Cette valeur contient « dossier de résolution » pour le fournisseur (autrement dit, le dossier du projet pour la compilation ou le dossier qui contient un script), la liste d'assemblys référencés, et d'autres informations.

  • Invalidation

    Les fournisseurs peuvent déclencher des signaux d'invalidation pour informer le service de langage F# que les hypothèses incluses dans le schéma a peut-être changé. Lorsque invalidation se produit, un typecheck est refait si le fournisseur est hébergé dans Visual Studio. Le signal sera ignoré lorsque le fournisseur est hébergé dans F# interactive ou par le compilateur F# (fsc.exe).

  • Les informations de schéma de mise en cache

    Les fournisseurs doivent souvent mettre en cache l'accès aux informations de schéma. Les données en mémoire cache doivent être stockées à l'aide d'un nom de fichier fourni comme paramètre statique ou en tant que données de l'utilisateur. Un exemple de mise en cache de schémas est le paramètre d' LocalSchemaFile dans le type fournisseurs dans l'assembly d' FSharp.Data.TypeProviders. Dans l'implémentation de ces fournisseurs, ce paramètre statique dirige le type fournisseur pour utiliser les informations de schéma dans le fichier local spécifié au lieu d'accéder à la source de données sur le réseau. Pour utiliser les informations de schéma mises en cache, vous devez également définir le paramètre statique ForceUpdate à false. Vous pouvez utiliser une technique similaire d'activer l'accès aux données en ligne et hors connexion.

  • Assembly de charge

    Lorsque vous compilez un fichier .dll ou .exe, le fichier .dll de stockage pour les types générés de manière statique est incorporé dans l'assembly résultant. Ce lien est créé en copiant les définitions de type (IL) de langage intermédiaire et toutes les ressources managées de l'assembly de charge dans l'assemblage final. Lorsque vous utilisez F# interactive, le fichier .dll de support n'est pas copié et est plutôt chargé directement dans le processus interactif F#.

  • Exceptions et diagnostics au type fournisseurs

    Toutes les utilisations de tous les membres des types fournis peuvent lever des exceptions. Dans tous les cas, si un type fournisseur lève une exception, le compilateur hôte attributs l'erreur en un type spécifique de fournisseur.

    • Le type exceptions de fournisseur ne doit jamais provoquer des erreurs internes du compilateur.

    • Le type fournisseurs ne peut pas signaler les avertissements.

    • Lorsqu'un type fournisseur est hébergé dans le compilateur F#, un environnement de développement F#, ou F# interactive, toutes les exceptions à ce fournisseur sont interceptées. La propriété de message est toujours le texte d'erreur, et aucune trace de la pile n'apparaît. Si vous souhaitez lever une exception, vous pouvez lever les exemples suivants :

Fourniture des types générés

Jusqu'à présent, ce document a montré comment fournir des types supprimés. Vous pouvez également utiliser le mécanisme type de fournisseur en F# pour fournir les types générés, qui sont ajoutés comme de vraies définitions de type. NET dans le programme utilisateur. Vous devez faire référence aux types fournis générés à l'aide d'une définition de type.

open Microsoft.FSharp.TypeProviders 

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

Le code ProvidedTypes-0.2 d'assistance qui fait partie de la version F# 3.0 uniquement limité en charge de fournir les types générés. Les instructions suivantes doivent être remplies pour une définition de type générée :

  • IsErased doit être définit sur false.

  • Le fournisseur doit avoir un assembly qui a un fichier .dll réel .NET en charge avec un fichier .dll correspondant sur le disque.

Vous devez également appeler ConvertToGenerated sur un type fourni par racine qui est imbriqué des types formulaire fermé un ensemble de types générés. Cet appel effectue la définition de type fournie donnée et ses définitions de type imbriquées dans un assembly et ajuste la propriété d' Assembly de tout fournissait des définitions de type pour retourner cet assembly. L'assembly est émis uniquement lorsque le type de propriété d'assembly à la racine est accessible pour la première fois. Le compilateur F# hôte accède à cette propriété lorsqu'il traite une déclaration de type générative pour le type.

Règles et limitations

Lorsque vous écrivez le type fournisseurs, conservez les règles et les limitations suivantes à l'esprit.

  • Si les types doivent être accessibles.

    Tous fournissaient des types doivent être accessibles des types non imbriqués. Les types imbriqués sont pas spécifiés dans l'appel au constructeur d' TypeProviderForNamespaces ou un appel à AddNamespace. Par exemple, si le fournisseur fournit un type StaticClass.P : T, vous devez vérifier que T est un type non imbriqué ou imbriqué sous un seul.

    Par exemple, certains fournisseurs ont une classe statique telle qu' DataTypes qui contiennent ces types T1, T2, T3, .... Autrement, l'erreur indique qu'une référence vers un type T dans l'assembly A a été trouvé, mais ce type ne peut pas être trouvé dans cette assembly. Si cette erreur apparaît, vérifiez que tous vos sous-types peuvent être atteints des types de fournisseur. Remarque : Ces types d' T1, T2, T3... sont appelés types de démarrer. N'oubliez pas de les mettre dans un espace de noms accessible ou un type parent.

  • Limitations du mécanisme type de fournisseur

    Le mécanisme type de fournisseur dans F# présente les limitations suivantes :

    • L'infrastructure sous-jacente pour le type fournisseurs en F# ne prend pas fourni les types génériques ou si les méthodes génériques.

    • Le mécanisme ne prend pas en charge les types imbriqués avec des paramètres statiques.

  • Limitations du code de stockage de ProvidedTypes

    Le code de prise en charge d' ProvidedTypes a des règles et des restrictions suivantes :

    1. Si des propriétés avec les accesseurs Get ni indexés des accesseurs get et ne sont pas implémentées.

    2. Si des événements ne sont pas implémentés.

    3. Les types et les objets fournis d'informations doivent être utilisés uniquement pour le mécanisme type de fournisseur en F#. Ils ne sont pas plus généralement utilisées comme objets System.Type.

    4. Les éléments que vous pouvez utiliser des quotations qui définissent des implémentations de méthode ont plusieurs restrictions. Faites référence au code source de ProvidedTypes-Version pour identifier les éléments qui sont pris en charge dans les quotations.

  • Les fournisseurs de type doivent générer des assemblys de sortie qui sont des fichiers .dll, pas fichiers .exe.

Conseils de développement

Vous trouverez les conseils suivants utiles lors du processus de développement.

  • Lancez deux instances de Visual Studio. Vous pouvez développer le type de fournisseur dans une instance et tester le fournisseur dans l'autre car le test IDE prendra un verrou sur le fichier .dll qui empêché le type fournisseur d'être régénéré. Par conséquent, vous devez fermer la deuxième instance de Visual Studio pendant que le fournisseur est généré en premier lieu, puis vous devez rouvrir la deuxième instance après que le fournisseur est généré.

  • Déboguez le type fournisseurs en utilisant des appels de fsc.exe. Vous pouvez appeler les fournisseurs de type à l'aide des outils suivants :

    • fsc.exe (le compilateur de ligne de commande F#)

    • fsi.exe (le compilateur interactif F#)

    • devenv.exe (Visual Studio)

    Vous pouvez souvent déboguer le type fournisseurs le plus facilement à l'aide de fsc.exe sur un fichier de script de test (par exemple, script.fsx). Vous pouvez activer un débogueur à partir d'une invite de commandes.

    devenv /debugexe fsc.exe script.fsx
    

    Vous pouvez utiliser copie--stdout à l'enregistrement.

Voir aussi

Autres ressources

Fournisseurs de type