Partilhar via


Tutorial: Criar um provedor de tipo

O mecanismo de provedor de tipo em F# é uma parte significativa de seu suporte para programação rica em informações. Este tutorial explica como criar seus próprios provedores de tipo, orientando-o através do desenvolvimento de vários provedores de tipo simples para ilustrar os conceitos básicos. Para obter mais informações sobre o mecanismo de provedor de tipo em F#, consulte Provedores de tipo.

O ecossistema F# contém uma variedade de tipos de provedores para serviços de dados corporativos e de Internet comumente usados. Por exemplo:

  • FSharp.Data inclui provedores de tipo para formatos de documento JSON, XML, CSV e HTML.

  • SwaggerProvider inclui dois provedores de tipo generativo que geram modelo de objeto e clientes HTTP para APIs descritas pelos esquemas OpenApi 3.0 e Swagger 2.0.

  • FSharp.Data.SqlClient tem um conjunto de provedores de tipo para incorporação verificada em tempo de compilação de T-SQL em F#.

Você pode criar provedores de tipo personalizados ou pode fazer referência a provedores de tipo que outros criaram. Por exemplo, sua organização pode ter um serviço de dados que forneça um grande e crescente número de conjuntos de dados nomeados, cada um com seu próprio esquema de dados estável. Você pode criar um provedor de tipo que lê os esquemas e apresenta os conjuntos de dados atuais ao programador de uma maneira fortemente tipada.

Antes de começar

O mecanismo do provedor de tipos é projetado principalmente para injetar dados estáveis e espaços de informações de serviço na experiência de programação do F#.

Esse mecanismo não foi projetado para injetar espaços de informação cujo esquema muda durante a execução do programa de maneiras que são relevantes para a lógica do programa. Além disso, o mecanismo não foi projetado para metaprogramação intra-linguagem, embora esse domínio contenha alguns usos válidos. Você deve usar esse mecanismo apenas quando necessário e quando o desenvolvimento de um provedor de tipo rende um valor muito alto.

Você deve evitar escrever um provedor de tipo onde um esquema não está disponível. Da mesma forma, você deve evitar escrever um provedor de tipo onde uma biblioteca .NET comum (ou até mesmo existente) seria suficiente.

Antes de começar, você pode fazer as seguintes perguntas:

  • Você tem um esquema para sua fonte de informação? Em caso afirmativo, qual é o mapeamento para o sistema do tipo F# e .NET?

  • Você pode usar uma API existente (digitada dinamicamente) como ponto de partida para sua implementação?

  • Você e sua organização terão usos suficientes do provedor de tipos para fazer a escrita valer a pena? Uma biblioteca .NET normal atenderia às suas necessidades?

  • Quanto mudará o seu esquema?

  • Vai mudar durante a codificação?

  • Vai mudar entre sessões de codificação?

  • Vai mudar durante a execução do programa?

Os provedores de tipo são mais adequados para situações em que o esquema é estável em tempo de execução e durante o tempo de vida do código compilado.

Um provedor de tipo simples

Este exemplo é Samples.HelloWorldTypeProvider, semelhante aos exemplos no examples diretório do F# Type Provider SDK. O provedor disponibiliza um "espaço de tipo" que contém 100 tipos apagados, como mostra o código a seguir, usando a sintaxe de assinatura F# e omitindo os detalhes para todos, exceto Type1. Para obter mais informações sobre tipos apagados, consulte Detalhes sobre tipos fornecidos apagados posteriormente neste tópico.

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

Observe que o conjunto de tipos e membros fornecidos é conhecido estaticamente. Este exemplo não aproveita a capacidade dos provedores de fornecer tipos que dependem de um esquema. A implementação do provedor de tipo é descrita no código a seguir, e os detalhes são abordados em seções posteriores deste tópico.

Aviso

Pode haver diferenças entre este código e os exemplos online.

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

Para usar esse provedor, abra uma instância separada do Visual Studio, crie um script F# e adicione uma referência ao provedor do seu script usando #r como mostra o código a seguir:

#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

Em seguida, procure os tipos sob o Samples.HelloWorldTypeProvider namespace que o provedor de tipo gerou.

Antes de recompilar o provedor, certifique-se de ter fechado todas as instâncias do Visual Studio e F# Interactive que estão usando a DLL do provedor. Caso contrário, ocorrerá um erro de compilação porque a DLL de saída será bloqueada.

Para depurar esse provedor usando instruções de impressão, crie um script que exponha um problema com o provedor e, em seguida, use o seguinte código:

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

Para depurar esse provedor usando o Visual Studio, abra o prompt de comando do desenvolvedor para Visual Studio com credenciais administrativas e execute o seguinte comando:

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

Como alternativa, abra o Visual Studio, abra o menu Depurar, escolha Debug/Attach to process…e anexe a outro devenv processo onde você está editando seu script. Usando esse método, você pode direcionar mais facilmente uma lógica específica no provedor de tipo digitando interativamente expressões na segunda instância (com IntelliSense completo e outros recursos).

Você pode desativar a depuração Just My Code para identificar melhor os erros no código gerado. Para obter informações sobre como habilitar ou desabilitar esse recurso, consulte Navegando pelo código com o depurador. Além disso, você também pode definir a captura de exceção de primeira chance abrindo o Debug menu e, em seguida, escolhendo Exceptions ou escolhendo as teclas Ctrl+Alt+E para abrir a Exceptions caixa de diálogo. Nessa caixa de diálogo, em Common Language Runtime Exceptions, marque a caixa de Thrown seleção.

Implementação do Type Provider

Esta seção o orienta pelas seções principais da implementação do provedor de tipo. Primeiro, você define o tipo para o próprio provedor de tipo personalizado:

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

Esse tipo deve ser público e você deve marcá-lo com o atributo TypeProvider para que o compilador reconheça o provedor de tipo quando um projeto F# separado fizer referência ao assembly que contém o tipo. O parâmetro config é opcional e, se presente, contém informações contextuais de configuração para a instância do provedor de tipo que o compilador F# cria.

Em seguida, implemente a interface ITypeProvider . Nesse caso, você usa o TypeProviderForNamespaces tipo da ProvidedTypes API como um tipo base. Esse tipo auxiliar pode fornecer uma coleção finita de namespaces fornecidos ansiosamente, cada um dos quais contém diretamente um número finito de tipos fixos e ansiosamente fornecidos. Nesse contexto, o provedor gera ansiosamente tipos, mesmo que eles não sejam necessários ou usados.

inherit TypeProviderForNamespaces(config)

Em seguida, defina valores privados locais que especifiquem o namespace para os tipos fornecidos e localize o próprio assembly do provedor de tipos. Esse assembly é usado posteriormente como o tipo pai lógico dos tipos apagados que são fornecidos.

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

Em seguida, crie uma função para fornecer cada um dos tipos Type1... Tipo100. Esta função é explicada em mais detalhes mais adiante neste tópico.

let makeOneProvidedType (n:int) = …

Em seguida, gere os 100 tipos fornecidos:

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

Em seguida, adicione os tipos como um namespace fornecido:

do this.AddNamespace(namespaceName, types)

Finalmente, adicione um atributo assembly que indica que você está criando uma DLL de provedor de tipo:

[<assembly:TypeProviderAssembly>]
do()

Fornecendo um tipo e seus membros

A makeOneProvidedType função faz o trabalho real de fornecer um dos tipos.

let makeOneProvidedType (n:int) =
…

Esta etapa explica a implementação desta função. Primeiro, crie o tipo fornecido (por exemplo, Type1, quando n = 1, ou Type57, quando 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>)

Deve ter em atenção os seguintes pontos:

  • Este tipo fornecido é apagado. Como você indica que o tipo base é obj, as instâncias aparecerão como valores do tipo obj no código compilado.

  • Ao especificar um tipo não aninhado, você deve especificar o assembly e o namespace. Para tipos apagados, o assembly deve ser o próprio assembly do provedor de tipo.

Em seguida, adicione documentação XML ao tipo. Esta documentação está atrasada, ou seja, é calculada sob demanda se o compilador do host precisar dela.

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

Em seguida, você adiciona uma propriedade estática fornecida ao tipo:

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

Obter esta propriedade será sempre avaliado para a string "Olá!". A GetterCode propriedade for the usa uma cotação F#, que representa o código que o compilador host gera para obter a propriedade. Para obter mais informações sobre cotações, consulte Cotações de código (F#).

Adicione documentação XML à propriedade.

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

Agora anexe a propriedade fornecida ao tipo fornecido. Você deve anexar um membro fornecido a um e apenas um tipo. Caso contrário, o membro nunca estará acessível.

t.AddMember staticProp

Agora crie um construtor fornecido que não usa parâmetros.

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

O InvokeCode para o construtor retorna uma cotação F#, que representa o código que o compilador host gera quando o construtor é chamado. Por exemplo, você pode usar o seguinte construtor:

new Type10()

Uma instância do tipo fornecido será criada com dados subjacentes "Os dados do objeto". O código citado inclui uma conversão para obj porque esse tipo é a eliminação desse tipo fornecido (como você especificou quando declarou o tipo fornecido).

Adicione documentação XML ao construtor e adicione o construtor fornecido ao tipo fornecido:

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

t.AddMember ctor

Crie um segundo construtor fornecido que usa um parâmetro:

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

O InvokeCode construtor for retorna novamente uma cotação F#, que representa o código que o compilador host gerou para uma chamada para o método. Por exemplo, você pode usar o seguinte construtor:

new Type10("ten")

Uma instância do tipo fornecido é criada com dados subjacentes "dez". Você já deve ter notado que a InvokeCode função retorna uma cotação. A entrada para esta função é uma lista de expressões, uma por parâmetro do construtor. Nesse caso, uma expressão que representa o valor de parâmetro único está disponível em args[0]. O código de uma chamada para o construtor coage o valor de retorno para o tipo objapagado. Depois de adicionar o segundo construtor fornecido ao tipo, você cria uma propriedade de instância fornecida:

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

Obter essa propriedade retornará o comprimento da cadeia de caracteres, que é o objeto de representação. A GetterCode propriedade retorna uma cotação F# que especifica o código que o compilador host gera para obter a propriedade. Como InvokeCode, a GetterCode função retorna uma cotação. O compilador host chama essa função com uma lista de argumentos. Nesse caso, os argumentos incluem apenas a única expressão que representa a instância na qual o getter está sendo chamado, que você pode acessar usando args[0]. A implementação de então emenda na cotação de resultado no tipo objapagado , e um cast é usado para satisfazer o mecanismo do compilador para verificar os tipos de que o objeto é uma cadeia de GetterCode caracteres. A próxima parte do fornece um método de makeOneProvidedType instância com um parâmetro.

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

Finalmente, crie um tipo aninhado que contenha 100 propriedades aninhadas. A criação desse tipo aninhado e suas propriedades é atrasada, ou seja, computada sob demanda.

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

Detalhes sobre os tipos fornecidos apagados

O exemplo nesta seção fornece apenas tipos fornecidos apagados, que são particularmente úteis nas seguintes situações:

  • Quando você está escrevendo um provedor para um espaço de informações que contém apenas dados e métodos.

  • Quando você está escrevendo um provedor onde semântica precisa do tipo tempo de execução não é crítica para o uso prático do espaço de informações.

  • Quando você está escrevendo um provedor para um espaço de informações que é tão grande e interconectado que não é tecnicamente viável gerar tipos .NET reais para o espaço de informações.

Neste exemplo, cada tipo fornecido é apagado para digitar obj, e todos os usos do tipo aparecerão como tipo obj em código compilado. Na verdade, os objetos subjacentes nesses exemplos são cadeias de caracteres, mas o tipo aparecerá como System.Object no código compilado do .NET. Como em todos os usos de eliminação de tipos, você pode usar boxe explícito, unboxing e casting para subverter tipos apagados. Nesse caso, uma exceção de transmissão que não é válida pode resultar quando o objeto é usado. Um tempo de execução do provedor pode definir seu próprio tipo de representação privada para ajudar a proteger contra falsas representações. Não é possível definir tipos apagados no próprio F#. Apenas os tipos fornecidos podem ser apagados. Você deve entender as ramificações, tanto práticas quanto semânticas, do uso de tipos apagados para seu provedor de tipos ou um provedor que fornece tipos apagados. Um tipo apagado não tem nenhum tipo .NET real. Portanto, você não pode fazer uma reflexão precisa sobre o tipo, e você pode subverter tipos apagados se você usar transmissões de tempo de execução e outras técnicas que dependem da semântica exata do tipo de tempo de execução. A subversão de tipos apagados frequentemente resulta em exceções de conversão de tipo em tempo de execução.

Escolhendo representações para tipos fornecidos apagados

Para alguns usos de tipos fornecidos apagados, nenhuma representação é necessária. Por exemplo, o tipo fornecido apagado pode conter apenas propriedades estáticas e membros e nenhum construtor, e nenhum método ou propriedade retornaria uma instância do tipo. Se você puder acessar instâncias de um tipo fornecido apagado, deverá considerar as seguintes perguntas:

O que é o apagamento de um tipo fornecido?

  • A eliminação de um tipo fornecido é como o tipo aparece no código .NET compilado.

  • A eliminação de um tipo de classe apagado fornecido é sempre o primeiro tipo base não apagado na cadeia de herança do tipo.

  • O apagamento de um tipo de interface apagado fornecido é sempre System.Object.

Quais são as representações de um tipo fornecido?

  • O conjunto de objetos possíveis para um tipo fornecido apagado é chamado de suas representações. No exemplo neste documento, as representações de todos os tipos Type1..Type100 fornecidos apagados são sempre objetos de cadeia de caracteres.

Todas as representações de um tipo fornecido devem ser compatíveis com o apagamento do tipo fornecido. (Caso contrário, o compilador F# dará um erro para um uso do provedor de tipo, ou o código .NET não verificável que não é válido será gerado. Um provedor de tipo não é válido se retornar um código que fornece uma representação que não é válida.)

Você pode escolher uma representação para objetos fornecidos usando uma das seguintes abordagens, ambas muito comuns:

  • Se você estiver simplesmente fornecendo um wrapper fortemente tipado sobre um tipo .NET existente, geralmente faz sentido para o seu tipo apagar para esse tipo, usar instâncias desse tipo como representações ou ambos. Essa abordagem é apropriada quando a maioria dos métodos existentes nesse tipo ainda faz sentido ao usar a versão fortemente tipada.

  • Se você quiser criar uma API que difere significativamente de qualquer API .NET existente, faz sentido criar tipos de tempo de execução que serão a eliminação de tipo e representações para os tipos fornecidos.

O exemplo neste documento usa cadeias de caracteres como representações de objetos fornecidos. Frequentemente, pode ser apropriado usar outros objetos para representações. Por exemplo, você pode usar um dicionário como um saco de propriedades:

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

Como alternativa, você pode definir um tipo em seu provedor de tipo que será usado em tempo de execução para formar a representação, juntamente com uma ou mais operações de tempo de execução:

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

Os membros fornecidos podem então construir instâncias deste tipo de objeto:

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

Nesse caso, você pode (opcionalmente) usar esse tipo como a eliminação de tipo, especificando esse tipo como o baseType ao construir o ProvidedTypeDefinition:

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

Principais lições

A seção anterior explicou como criar um provedor de tipo de apagamento simples que fornece uma variedade de tipos, propriedades e métodos. Esta seção também explicou o conceito de eliminação de tipo, incluindo algumas das vantagens e desvantagens de fornecer tipos apagados de um provedor de tipos, e discutiu representações de tipos apagados.

Um provedor de tipo que usa parâmetros estáticos

A capacidade de parametrizar provedores de tipo por dados estáticos permite muitos cenários interessantes, mesmo nos casos em que o provedor não precisa acessar nenhum dado local ou remoto. Nesta seção, você aprenderá algumas técnicas básicas para montar esse provedor.

Tipo verificado Regex Provider

Imagine que você deseja implementar um provedor de tipo para expressões regulares que encapsula as bibliotecas .NET Regex em uma interface que fornece as seguintes garantias em tempo de compilação:

  • Verificar se uma expressão regular é válida.

  • Fornecer propriedades nomeadas em correspondências baseadas em nomes de grupo na expressão regular.

Esta seção mostra como usar provedores de tipo para criar um RegexTyped tipo que o padrão de expressão regular parametriza para fornecer esses benefícios. O compilador relatará um erro se o padrão fornecido não for válido, e o provedor de tipo pode extrair os grupos do padrão para que você possa acessá-los usando propriedades nomeadas em correspondências. Ao projetar um provedor de tipo, você deve considerar como sua API exposta deve parecer para os usuários finais e como esse design será traduzido para o código .NET. O exemplo a seguir mostra como usar essa API para obter os componentes do código de área:

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"

O exemplo a seguir mostra como o provedor de tipo traduz essas chamadas:

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"

Tenha em conta os seguintes pontos:

  • O tipo Regex padrão representa o tipo parametrizado RegexTyped .

  • O RegexTyped construtor resulta em uma chamada para o construtor Regex, passando o argumento de tipo estático para o padrão.

  • Os resultados do Match método são representados pelo tipo padrão Match .

  • Cada grupo nomeado resulta em uma propriedade fornecida e o acesso à propriedade resulta no uso de um indexador na coleção de Groups uma correspondência.

O código a seguir é o núcleo da lógica para implementar tal provedor, e este exemplo omite a adição de todos os membros ao tipo fornecido. Para obter informações sobre cada membro adicionado, consulte a seção apropriada mais adiante neste tópico.

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

Tenha em conta os seguintes pontos:

  • O provedor de tipo usa dois parâmetros estáticos: o pattern, que é obrigatório, e o options, que são opcionais (porque um valor padrão é fornecido).

  • Depois que os argumentos estáticos são fornecidos, você cria uma instância da expressão regular. Esta instância lançará uma exceção se o Regex estiver malformado e esse erro será relatado aos usuários.

  • Dentro do retorno de DefineStaticParameters chamada, você define o tipo que será retornado depois que os argumentos forem fornecidos.

  • Este código é definido HideObjectMethods como true para que a experiência do IntelliSense permaneça simplificada. Esse atributo faz com que os Equalsmembros , GetHashCode, Finalizee GetType sejam suprimidos das listas do IntelliSense para um objeto fornecido.

  • Você usa obj como o tipo base do método, mas usará um Regex objeto como a representação de tempo de execução desse tipo, como mostra o próximo exemplo.

  • A chamada para o Regex construtor lança um ArgumentException quando uma expressão regular não é válida. O compilador captura essa exceção e relata uma mensagem de erro para o usuário em tempo de compilação ou no editor do Visual Studio. Essa exceção permite que expressões regulares sejam validadas sem executar um aplicativo.

O tipo definido acima ainda não é útil porque não contém nenhum método ou propriedade significativa. Primeiro, adicione um método estático IsMatch :

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

O código anterior define um método IsMatch, que usa uma cadeia de caracteres como entrada e retorna um boolarquivo . A única parte complicada é o uso do argumento dentro da argsInvokeCode definição. Neste exemplo, args é uma lista de citações que representa os argumentos para esse método. Se o método for um método de instância, o primeiro argumento representa o this argumento. No entanto, para um método estático, os argumentos são todos apenas os argumentos explícitos para o método. Observe que o tipo do valor cotado deve corresponder ao tipo de retorno especificado (neste caso, bool). Observe também que esse código usa o AddXmlDoc método para garantir que o método fornecido também tenha documentação útil, que você pode fornecer através do IntelliSense.

Em seguida, adicione um método Match de instância. No entanto, esse método deve retornar um valor de um tipo fornecido Match para que os grupos possam ser acessados de forma fortemente tipada. Assim, você primeiro declara o Match tipo. Como esse tipo depende do padrão que foi fornecido como um argumento estático, esse tipo deve ser aninhado dentro da definição de tipo parametrizado:

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

ty.AddMember matchTy

Em seguida, você adiciona uma propriedade ao tipo Correspondência para cada grupo. Em tempo de execução, uma correspondência é representada como um Match valor, portanto, a cotação que define a propriedade deve usar a propriedade indexada Groups para obter o grupo relevante.

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

Novamente, observe que você está adicionando documentação XML à propriedade fornecida. Observe também que uma propriedade pode ser lida se uma GetterCode função for fornecida, e a propriedade pode ser gravada se uma SetterCode função for fornecida, portanto, a propriedade resultante será somente leitura.

Agora você pode criar um método de instância que retorna um valor desse Match tipo:

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

Como você está criando um método de instância, args[0] representa a RegexTyped instância na qual o método está sendo chamado e args[1] é o argumento de entrada.

Finalmente, forneça um construtor para que instâncias do tipo fornecido possam ser criadas.

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

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

ty.AddMember ctor

O construtor simplesmente apaga para a criação de uma instância padrão do .NET Regex, que é novamente encaixotada em um objeto porque obj é a eliminação do tipo fornecido. Com essa alteração, o uso da API de exemplo especificado anteriormente no tópico funciona conforme o esperado. O código a seguir é completo e 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>,
                        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 ()

Principais lições

Esta seção explicou como criar um provedor de tipo que opera em seus parâmetros estáticos. O provedor verifica o parâmetro estático e fornece operações com base em seu valor.

Um provedor de tipo que é apoiado por dados locais

Freqüentemente, você pode querer que os provedores de tipo apresentem APIs baseadas não apenas em parâmetros estáticos, mas também em informações de sistemas locais ou remotos. Esta seção discute provedores de tipo baseados em dados locais, como arquivos de dados locais.

Provedor de arquivos CSV simples

Como um exemplo simples, considere um provedor de tipo para acessar dados científicos no formato CSV (Comma Separated Value). Esta seção pressupõe que os arquivos CSV contenham uma linha de cabeçalho seguida por dados de ponto flutuante, como ilustra a tabela a seguir:

Distância (metro) Tempo (segundo)
50.0 3.7
100.0 5,2
150.0 6.4

Esta seção mostra como fornecer um tipo que você pode usar para obter linhas com uma Distance propriedade do tipo float<meter> e uma Time propriedade do tipo float<second>. Para simplificar, são feitas as seguintes suposições:

  • Os nomes de cabeçalho não têm unidade ou têm a forma "Nome (unidade)" e não contêm vírgulas.

  • As unidades são todas as unidades do System International (SI) como o módulo FSharp.Data.UnitSystems.SI.UnitNames Module (F#) define.

  • As unidades são todas simples (por exemplo, metro) e não compostas (por exemplo, metro/segundo).

  • Todas as colunas contêm dados de ponto flutuante.

Um fornecedor mais completo afrouxaria essas restrições.

Mais uma vez, o primeiro passo é considerar como a API deve parecer. Dado um info.csv arquivo com o conteúdo da tabela anterior (em formato separado por vírgula), os usuários do provedor devem ser capazes de escrever código semelhante ao exemplo a seguir:

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

Nesse caso, o compilador deve converter essas chamadas em algo como o exemplo a seguir:

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

A tradução ideal exigirá que o provedor de tipo defina um tipo real CsvFile na montagem do provedor de tipo. Os provedores de tipos geralmente dependem de alguns tipos e métodos auxiliares para envolver a lógica importante. Como as medidas são apagadas em tempo de execução, você pode usar a float[] como o tipo apagado para uma linha. O compilador tratará colunas diferentes como tendo diferentes tipos de medidas. Por exemplo, a primeira coluna do nosso exemplo tem o tipo float<meter>, e a segunda tem float<second>. No entanto, a representação apagada pode permanecer bastante simples.

O código a seguir mostra o núcleo da implementação.

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

Observe os seguintes pontos sobre a implementação:

  • Construtores sobrecarregados permitem que o arquivo original ou um que tenha um esquema idêntico seja lido. Esse padrão é comum quando você escreve um provedor de tipo para fontes de dados locais ou remotas, e esse padrão permite que um arquivo local seja usado como modelo para dados remotos.

  • Você pode usar o valor TypeProviderConfig que é passado para o construtor do provedor de tipo para resolver nomes de arquivo relativos.

  • Você pode usar o AddDefinitionLocation método para definir o local das propriedades fornecidas. Portanto, se você usar Go To Definition em uma propriedade fornecida, o arquivo CSV será aberto no Visual Studio.

  • Você pode usar o ProvidedMeasureBuilder tipo para procurar as unidades SI e gerar os tipos relevantes float<_> .

Principais lições

Esta seção explicou como criar um provedor de tipo para uma fonte de dados local com um esquema simples contido na própria fonte de dados.

Ir mais longe

As secções seguintes incluem sugestões para um estudo mais aprofundado.

Uma olhada no código compilado para tipos apagados

Para dar uma ideia de como o uso do provedor de tipo corresponde ao código emitido, examine a seguinte função usando o HelloWorldTypeProvider que é usado anteriormente neste tópico.

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

Aqui está uma imagem do código resultante descompilado usando 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

Como mostra o exemplo, todas as menções ao tipo Type1 e à InstanceProperty propriedade foram apagadas, deixando apenas operações nos tipos de tempo de execução envolvidos.

Convenções de design e nomenclatura para provedores de tipo

Observe as seguintes convenções ao criar provedores de tipo.

Provedores para protocolos de conectividade Em geral, os nomes da maioria das DLLs de provedor para protocolos de conectividade de dados e serviços, como conexões OData ou SQL, devem terminar em TypeProvider ou TypeProviders. Por exemplo, use um nome de DLL semelhante à seguinte cadeia de caracteres:

Fabrikam.Management.BasicTypeProviders.dll

Verifique se os tipos fornecidos são membros do namespace correspondente e indique o protocolo de conectividade que você implementou:

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

Fornecedores de serviços públicos para codificação geral. Para um provedor de tipo de utilitário, como aquele para expressões regulares, o provedor de tipo pode fazer parte de uma biblioteca base, como mostra o exemplo a seguir:

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

Nesse caso, o tipo fornecido apareceria em um ponto apropriado de acordo com as convenções normais de design do .NET:

  open Fabrikam.Core.Text.RegexTyped

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

Fontes de dados Singleton. Alguns provedores de tipo se conectam a uma única fonte de dados dedicada e fornecem apenas dados. Nesse caso, você deve soltar o sufixo TypeProvider e usar convenções normais para nomenclatura .NET:

#r "Fabrikam.Data.Freebase.dll"

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

Para obter mais informações, consulte a GetConnection convenção de design descrita posteriormente neste tópico.

Padrões de design para provedores de tipo

As seções a seguir descrevem padrões de design que você pode usar ao criar provedores de tipo.

O padrão de design GetConnection

A maioria dos provedores de tipo deve ser gravada para usar o GetConnection padrão usado pelos provedores de tipo em FSharp.Data.TypeProviders.dll, como mostra o exemplo a seguir:

#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

Provedores de tipo apoiados por dados e serviços remotos

Antes de criar um provedor de tipos apoiado por dados e serviços remotos, você deve considerar uma série de problemas inerentes à programação conectada. Estas questões incluem as seguintes considerações:

  • mapeamento de esquema

  • vivacidade e invalidação na presença de mudança de esquema

  • Cache de esquema

  • Implementações assíncronas de operações de acesso a dados

  • consultas de suporte, incluindo consultas LINQ

  • Credenciais e autenticação

Este tópico não explora mais essas questões.

Técnicas de criação adicionais

Quando você escreve seus próprios provedores de tipo, convém usar as seguintes técnicas adicionais.

Criando tipos e membros sob demanda

A API ProvidedType atrasou versões de AddMember.

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

Essas versões são usadas para criar espaços sob demanda de tipos.

Fornecendo tipos de matriz e instanciações de tipo genérico

Você torna os membros fornecidos (cujas assinaturas incluem tipos de matriz, tipos byref e instanciações de tipos genéricos) usando o normal MakeArrayType, MakePointerTypee MakeGenericType em qualquer instância de Type, incluindo ProvidedTypeDefinitions.

Nota

Em alguns casos, você pode ter que usar o auxiliar em ProvidedTypeBuilder.MakeGenericType. Consulte a documentação do Type Provider SDK para obter mais detalhes.

Fornecimento de anotações da unidade de medida

A API ProvidedTypes fornece auxiliares para fornecer anotações de medida. Por exemplo, para fornecer o tipo float<kg>, use o seguinte código:

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

Para fornecer o tipo Nullable<decimal<kg/m^2>>, use o seguinte código:

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

Acessando recursos locais do projeto ou script-local

Cada instância de um provedor de tipo pode receber um valor durante a TypeProviderConfig construção. Esse valor contém a "pasta de resolução" para o provedor (ou seja, a pasta do projeto para a compilação ou o diretório que contém um script), a lista de assemblies referenciados e outras informações.

Anulação

Os provedores podem gerar sinais de invalidação para notificar o serviço de linguagem F# de que as suposições do esquema podem ter sido alteradas. Quando ocorre a invalidação, uma verificação de tipo é refeita se o provedor estiver sendo hospedado no Visual Studio. Esse sinal será ignorado quando o provedor estiver hospedado em F# Interactive ou pelo compilador F# (fsc.exe).

Informações de esquema de cache

Os provedores geralmente devem armazenar em cache o acesso às informações do esquema. Os dados armazenados em cache devem ser armazenados usando um nome de arquivo fornecido como um parâmetro estático ou como dados do usuário. Um exemplo de cache de esquema é o LocalSchemaFile parâmetro nos provedores de tipo no FSharp.Data.TypeProviders assembly. Na implementação desses provedores, esse parâmetro estático direciona o provedor de tipo para usar as informações de esquema no arquivo local especificado em vez de acessar a fonte de dados pela rede. Para usar informações de esquema armazenadas em cache, você também deve definir o parâmetro ForceUpdate static como false. Você pode usar uma técnica semelhante para habilitar o acesso a dados online e offline.

Montagem de apoio

Quando você compila um .dll arquivo ou .exe , o arquivo de .dll de backup para tipos gerados é vinculado estaticamente ao assembly resultante. Este link é criado copiando as definições de tipo de linguagem intermediária (IL) e quaisquer recursos gerenciados do assembly de suporte para o assembly final. Quando você usa o F# interativo, o arquivo de .dll de backup não é copiado e, em vez disso, é carregado diretamente no processo interativo do F#.

Exceções e diagnósticos de provedores de tipo

Todos os usos de todos os membros dos tipos fornecidos podem gerar exceções. Em todos os casos, se um provedor de tipo lançar uma exceção, o compilador de host atribui o erro a um provedor de tipo específico.

  • As exceções do provedor de tipo nunca devem resultar em erros internos do compilador.

  • Os provedores de tipo não podem relatar avisos.

  • Quando um provedor de tipo é hospedado no compilador F#, em um ambiente de desenvolvimento F# ou F# interativo, todas as exceções desse provedor são capturadas. A propriedade Message é sempre o texto de erro e nenhum rastreamento de pilha aparece. Se você vai lançar uma exceção, você pode lançar os seguintes exemplos: System.NotSupportedException, System.IO.IOException, System.Exception.

Fornecendo tipos gerados

Até agora, este documento explicou como fornecer tipos apagados. Você também pode usar o mecanismo de provedor de tipo em F# para fornecer tipos gerados, que são adicionados como definições de tipo .NET reais no programa dos usuários. Você deve fazer referência aos tipos fornecidos gerados usando uma definição de tipo.

open Microsoft.FSharp.TypeProviders

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

O código auxiliar ProvidedTypes-0.2 que faz parte da versão F# 3.0 tem suporte limitado apenas para fornecer tipos gerados. As instruções a seguir devem ser verdadeiras para uma definição de tipo gerada:

  • isErased deve ser definido como false.

  • O tipo gerado deve ser adicionado a um recém-construído ProvidedAssembly(), que representa um contêiner para fragmentos de código gerados.

  • O provedor deve ter um assembly que tenha um arquivo de .dll .NET de suporte real com um arquivo .dll correspondente no disco.

Regras e Limitações

Ao escrever provedores de tipo, tenha em mente as seguintes regras e limitações.

Os tipos fornecidos devem ser acessíveis

Todos os tipos fornecidos devem ser acessíveis a partir dos tipos não aninhados. Os tipos não aninhados são fornecidos na chamada para o TypeProviderForNamespaces construtor ou uma chamada para AddNamespace. Por exemplo, se o provedor fornecer um tipo StaticClass.P : T, você deve garantir que T seja um tipo não aninhado ou aninhado em um.

Por exemplo, alguns provedores têm uma classe estática, como DataTypes a que contém esses T1, T2, T3, ... tipos. Caso contrário, o erro diz que uma referência ao tipo T no assembly A foi encontrada, mas o tipo não pôde ser encontrado nesse assembly. Se esse erro aparecer, verifique se todos os seus subtipos podem ser acessados a partir dos tipos de provedor. Nota: Estes T1, T2, T3... tipos são referidos como os tipos on-the-fly . Lembre-se de colocá-los em um namespace acessível ou em um tipo pai.

Limitações do Mecanismo do Provedor de Tipo

O mecanismo de provedor de tipo em F# tem as seguintes limitações:

  • A infraestrutura subjacente para provedores de tipo em F# não suporta tipos genéricos fornecidos ou métodos genéricos fornecidos.

  • O mecanismo não suporta tipos aninhados com parâmetros estáticos.

Dicas de Desenvolvimento

Você pode achar as seguintes dicas úteis durante o processo de desenvolvimento:

Executar duas instâncias do Visual Studio

Você pode desenvolver o provedor de tipo em uma instância e testar o provedor na outra porque o IDE de teste terá um bloqueio no arquivo de .dll que impede que o provedor de tipo seja reconstruído. Assim, você deve fechar a segunda instância do Visual Studio enquanto o provedor é criado na primeira instância e, em seguida, você deve reabrir a segunda instância depois que o provedor é criado.

Depurar provedores de tipo usando invocações de fsc.exe

Você pode invocar provedores de tipo usando as seguintes ferramentas:

  • fsc.exe (O compilador de linha de comando F#)

  • fsi.exe (O compilador interativo F#)

  • devenv.exe (Visual Studio)

Muitas vezes, você pode depurar provedores de tipo mais facilmente usando fsc.exe em um arquivo de script de teste (por exemplo, script.fsx). Você pode iniciar um depurador a partir de um prompt de comando.

devenv /debugexe fsc.exe script.fsx

Você pode usar o registro print-to-stdout.

Consulte também