Tutorial: Criando um provedor de tipos (F#)
O mecanismo de provedor de tipo no F# 3.0 é uma parte significativa de seu suporte para programação rica de informações. Este tutorial explica como criar seus próprios provedores de tipo mostrando o 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 tipos.
O F# 3.0 contém vários provedores de tipo internos para a Internet e serviços de dados corporativos comumente usados. Esses provedores de tipo fornecem acesso simples e normal a bancos de dados relacionais SQL e serviços da rede de OData de e WSDL. Esses provedores também oferecem suporte ao uso de consultas de F# LINQ com essas fontes de dados.
Se necessário, você pode criar provedores de tipo personalizados ou referenciar os provedores de tipo criado por outros. Por exemplo, sua organização pode ter um serviço de dados que fornece um número grande e crescente de conjuntos de dados nomeados, cada um com seu próprio esquema estável de dados. Você pode criar um provedor de tipo que lê os esquemas e apresentará os conjuntos de dados atual para o programador de uma maneira fortemente tipada.
Antes de iniciar:
O mecanismo de provedor de tipo é projetado principalmente para injetar espaços de informações de dados e serviço estáveis na experiência de programação de F#.
Esse mecanismo não é criado para injetar espaços de informações cujo esquema é alterado durante a execução do programa em maneiras que são relevantes para a lógica do programa. Além de isso, o mecanismo não é criado para meta-programação de intra-linguagem, mesmo que o domínio contenha alguns usos válidos. Você deve usar esse mecanismo somente quando necessário e onde o desenvolvimento de um provedor de tipo produz valor muito alto.
Você deve evitar escrever um provedor de tipo onde um esquema que não está disponível. De mesma forma, você deve evitar escrever um provedor de tipo onde uma biblioteca comum (ou mesmo uma existente) .NET baste.
Antes de iniciar, você pode fazer as seguintes questões:
Você tem um esquema para sua fonte de informação? Em caso afirmativo, qual é o mapeamento no sistema de tipos F# e .NET?
Você pode usar a API existente (tipado dinamicamente) como um ponto de partida para sua implementação?
Você e sua organização têm usos suficientes do provedor de tipo para que escrever valha a pena? Uma biblioteca normal .NET atenderia suas necessidades?
Quanto seu esquema será alterado?
Ele será alterado durante a codificação?
Ele será alterado entre sessões de codificação?
Ele será alterado durante a execução do programa?
Provedores de tipo são adequam melhor a situações em que o esquema é estável em tempo de execução e durante o ciclo de vida de código compilado.
Um provedor de tipo simples
Este exemplo é Samples.HelloWorldTypeProvider no diretório de SampleProviders\Providers Bloco de F# 3,0 ativada do site do Codeplex. O provedor torna disponível um "espaço de tipo” que contém 100 tipos apagados, como mostra o código a seguir, usando a sintaxe da assinatura de F# e omitindo detalhes para todos exceto Type1. Para obter mais informações sobre tipos apagados, consulte Detalhes sobre tipos fornecidos apagada 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
/// 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 =
…
Observe que o conjunto de tipos e membros fornecidos é conhecido estaticamente. Este exemplo não aproveita a capacidade de provedores de fornecer os tipos que dependam de um esquema. A implementação do provedor de tipo é demarcada no código a seguir, e os detalhes são abordados nas seções posteriores de este tópico.
Aviso
Podem existir algumas pequenas diferenças de nomeação entre esse código e exemplos on-line.
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()
Para usar esse provedor, abra uma instância separada de Visual Studio 2012, crie um script F#, e então adicione uma referência do seu script para o provedor usando #r como mostra 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
Então procure os tipos sob o namespace Samples.HelloWorldTypeProvider que o provedor de tipo gerou.
Antes de você recompilar o provedor, certifique-se que você fechou todas as instâncias do Visual Studio e do F# Interativo que estão usando o DLL do provedor. Caso contrário, ocorrerá um erro de compilação porque o DLL de saída será bloqueado.
Para depurar este provedor usando instruções de impressão, faça um script que expõe um problema com o provedor, e depois use o código a seguir:
fsc.exe -r:bin\Debug\HelloWorldTypeProvider.dll script.fsx
Para depurar este provedor usando o Visual Studio, abra o prompt de comando do 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 de depuração, escolha Depuração/Anexar ao processo… e anexe a um outro processo devenv em que você está editando o script. Usando esse método, você pode facilmente utilizar a lógica específica do provedor de tipo interativamente digitando expressões na segunda instância (com o IntelliSense completo e outros recursos.)
Você pode desativar a depuração Just My Code para melhor identificar erros no código gerado. Para obter informações adicionais sobre como ativar ou desativar esse recurso, consulte Restrict a depuração apenas a my código. Além de isso, você pode também definir a captura de exceção de primeira chance abrindo o menu Depurar e então escolhendo Exceções ou escolhendo as chaves Ctrl+Alt+E para abrir a caixa de diálogo Exceções. Na caixa de diálogo, em Exceções do Common Language Runtime, selecione a caixa de seleção Iniciados.
Implementação de provedor de tipo
Esta seção guia você por seções principais da implementação do provedor do tipo. Primeiro, você define o tipo para o provedor personalizado do próprio tipo:
[<TypeProvider>]
type SampleTypeProvider(config: TypeProviderConfig) as this =
Este tipo deve ser público e você deve marcá-lo com o atributo TypeProvider de modo que o compilador reconhecerá o provedor do tipo quando um projeto F# separado faz referência ao assembly que contém o tipo. O parâmetro config é opcional e, se presente, contém informações de configuração contextual para a instância do provedor de tipo que o compilador de F# cria.
Em seguida, você deve implementar a interface ITypeProvider. Nesse caso, você usa o tipo TypeProviderForNamespaces do API ProvidedTypes como um tipo base. Este tipo auxiliar pode fornecer uma coleção finito de namespaces ansiosamente fornecidas, cada um de eles contém diretamente um número finito de fixo, tipos ansiosamente fornecidos. Nesse contexto, o provedor gera ansiosamente tipos mesmo que não sejam necessários ou usados.
inherit TypeProviderForNamespaces()
Em seguida, defina os valores locais particulares que especificam o namespace para os tipos fornecidos, e localiza o próprio assembly de provedor de tipo. Esse assembly é usado posteriormente como o tipo pai lógico de tipos apagada 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… Type100. Essa função é explicada em mais detalhes posteriormente neste tópico.
let makeOneProvidedType (n:int) = …
Em seguida, gera 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 do provedor do tipo:
[<assembly:TypeProviderAssembly>]
do()
Fornecendo um tipo e seus membros
A função makeOneProvidedType faz o trabalho real de fornecer um dos tipos.
let makeOneProvidedType (n:int) =
…
Esta etapa explica a implementação dessa função. Primeiro, crie um 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>)
Você deve observar os seguintes pontos:
Esse tipo fornecido é apagado. Uma vez que você indica que o tipo base é obj, as instâncias aparecerão como valores do tipo obj no código compilado.
Quando você especifica um tipo não aninhado, você deve especificar o assembly e o namespace. Para tipos apagados, o assembly deve ser o próprio assembly provedor de tipo.
Em seguida, adicione a documentação XML para o tipo. Esta documentação é tardia, isto é, calculada sob demanda se o host o compilador precisar.
t.AddXmlDocDelayed (fun () -> sprintf "This provided type %s" ("Type" + string n))
Em seguida você adiciona uma propriedade estática fornecida para o tipo:
let staticProp = ProvidedProperty(propertyName = "StaticProperty",
propertyType = typeof<string>,
IsStatic=true,
GetterCode= (fun args -> <@@ "Hello!" @@>))
Obter essa propriedade sempre avaliará com a cadeia de caracteres “Hello!”. O GetterCode para a propriedade usa uma citação F#, que representa o código que o compilador host gera para obter a propriedade. Para obter mais informações sobre as cotações, consulte Citações de código (F#).
Adicione a documentação XML para a propriedade.
staticProp.AddXmlDocDelayed(fun () -> "This is a static property")
Anexar agora a propriedade fornecida para o tipo fornecido. Você deve anexar um membro fornecido para um e somente um tipo. Caso contrário, o membro nunca será acessível.
t.AddMember staticProp
Crie agora um construtor fornecido que não leva nenhum parâmetro.
let ctor = ProvidedConstructor(parameters = [ ],
InvokeCode= (fun args -> <@@ "The object data" :> obj @@>))
O InvokeCode para o construtor retorna uma citação F#, que representa o código que o compilador host gera quando o construtor é chamado. Por exemplo, você pode usar o construtor a seguir:
new Type10()
Uma instância do tipo fornecido será criada com os dados subjacentes “Os dados do objeto”. O código cotado inclui uma conversão a obj porque esse tipo é a rasura desse tipo fornecido (como você especificou quando você declarou o tipo fornecido).
Adicione a documentação XML para o construtor, e adicione o construtor fornecido para o tipo fornecido:
ctor.AddXmlDocDelayed(fun () -> "This is a constructor")
t.AddMember ctor
Crie um segundo construtor fornecido que recebe um parâmetro:
let ctor2 =
ProvidedConstructor(parameters = [ ProvidedParameter("data",typeof<string>) ],
InvokeCode= (fun args -> <@@ (%%(args.[0]) : string) :> obj @@>))
O InvokeCode para o construtor retorna novamente uma citaçã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 construtor a seguir:
new Type10("ten")
Uma instância do tipo fornecido é criada com o dado subjacente “dez”. Você pode já ter observado que a função InvokeCode retorna um citação. A entrada dessa função é uma lista de expressões, uma por parâmetro do construtor. Nesse caso, uma expressão que representa o valor do parâmetro único está disponível em args.[0]. O código para uma chamada ao construtor força o valor de retorno para o tipo obj apagado. Após adicionar o segundo construtor fornecido para o tipo, você cria uma propriedade fornecida de instância:
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 propriedade GetterCode retorna uma citação de F# que especifica o código que o compilador host gera para obter a propriedade. Como InvokeCode, a função GetterCode retorna uma citaçã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 GetterCode então unir em citações de resultado no tipo apagado obj, e uma conversão são usadas para satisfazer o mecanismo de compilador para verificar tipos em que o objeto é uma cadeia de caracteres. A próxima parte de makeOneProvidedType fornece um método de 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 contém 100 propriedades aninhadas. A criação de esse tipo aninhado e suas propriedades é tardia, isto é, calculada sob demanda.
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
Detalhes sobre tipos fornecidos apagados
O exemplo nessa seção fornece somente tipos fornecidos apagados, que são particularmente úteis nas seguintes situações:
Quando você escrever um provedor para um espaço de informações que contém somente dados e métodos.
Quando você escrever um provedor onde a exata semântica do tipo de tempo de execução não será crítica para uso prático de espaço de informações.
Quando você escrever um provedor para um espaço de informações que é tão grande e interconectado que tecnicamente não é possível gerar tipos reais .NET para o espaço de informações.
Nesse exemplo, cada tipo fornecido é apagado para o tipo obj, e todos os usos do tipo aparecerão como o tipo obj no código compilado. Na verdade, os objetos subjacentes nesses exemplos são cadeias de caracteres, mas o tipo aparecerá como Object no código compilado .NET. Como com todos os usos de supressão de tipo, você pode usar o boxing explícito, unboxing e conversão para subverter tipos apagados. Nesse caso, uma exceção de conversão que não é válida pode ocorrer quando o objeto é usado. Um tempo de execução do provedor pode definir seu próprio tipo particular de representação para ajudar a proteger contra representações falsas. Você não pode definir tipos apagados no próprio F#. Somente tipos fornecidos podem ser apagados. Você deve compreender as ramificação, ambas práticas e semânticas, o uso de tipos apagados para seu provedor de tipo ou um provedor que fornece tipos apagados. Um tipo apagado não tem nenhum tipo real .NET. Portanto, você não pode fazer a reflexão exata sobre o tipo, e você pode subverter tipos apagados se você usar conversões de tempo de execução e outras técnicas que contam com a exata semântica de tipo em tempo de execução. A subversão de tipos apagados resulta com frequência em exceções de conversão de tipos 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ê pode alcançar instâncias de um tipo fornecido apagado, você deve considerar as seguintes questões:
O que é o apagamento de um tipo fornecido?
O apagamento de um tipo fornecido é como o tipo aparece no código compilado .NET.
O apagamento de um tipo fornecido apagado da classe é 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 Object.
Que são 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, representações de todos os tipos fornecidos apagados Type1..Type100 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, ou o compilador de F# fornecerá um erro para o uso de um provedor de tipo, ou o código não verificável .NET que não é válido será gerado. Um provedor de tipo não é válido que o código do que fornece uma representação que não é válido.)
Você pode escolher uma representação para objetos fornecidos usando qualquer uma das seguintes abordagens, ambas são muito comuns:
Se você está fornecendo simplesmente um wrapper fortemente tipado em um tipo existente .NET, às vezes faz sentido para seu tipo apagar a esse tipo, usar instâncias desse tipo como representações, ou ambos. Essa abordagem é apropriada quando a maioria dos métodos existentes sobre aquele tipo ainda fazem sentido quando usando a versão fortemente tipada.
Se você deseja criar uma API que difere significativamente do API .NET existente, faz sentido criar tipos de tempo de execução que serão o apagamento de tipo e as representações para 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 conjunto de propriedades:
ProvidedConstructor(parameters = [],
InvokeCode= (fun args -> <@@ (new Dictionary<string,obj>()) :> obj @@>))
Como alternativa, você pode definir um tipo em seu provedor do 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
Desde que os membros possam então criar instâncias deste tipo de objeto:
ProvidedConstructor(parameters = [],
InvokeCode= (fun args -> <@@ (new DataObject()) :> obj @@>))
Nesse caso, você pode (opcionalmente) usar esse tipo como o apagamento de tipo especificando o tipo como baseType quando construir ProvidedTypeDefinition:
ProvidedTypeDefinition(…, baseType = Some typeof<DataObject> )
…
ProvidedConstructor(…, InvokeCode = (fun args -> <@@ new DataObject() @@>), …)
Lições chave
A seção anterior explicou como criar um simples provedor de tipo de apagamento que fornece um intervalo de tipos, propriedades e métodos. Esta seção também explicou o conceito de apagamento de tipo, inclusive com algumas das vantagens e as desvantagens de fornecer tipos apagados de um provedor de tipo, 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 vários cenários interessantes, mesmo em casos quando o provedor não precisa acessar os dados locais ou remotos. Nesta seção, você aprenderá algumas técnicas básicas para montar um desses provedores.
Provedor de tipos verificados por Regex
Imagine que você deseja implementar um provedor de tipo para expressões regulares que envolve as bibliotecas .NET Regex em uma interface que fornece as seguintes garantias de tempo de compilação:
Verificação se uma expressão regular é válida.
Fornecimento de propriedades nomeadas nas correspondências que são baseadas em todos os nomes de grupo na expressão regular.
Esta seção mostra como usar provedores de tipo para criar um tipo RegExProviderType que o padrão de expressão regular parametriza para fornecer esses benefícios. O compilador reporta um erro se o padrão fornecido não é válido, e o provedor do tipo pode extrair os grupos do padrão para que você possa acessá-los usando propriedades nomeadas em correspondências. Quando você cria um provedor de tipo, você deve considerar como a API deve ser exposta aos usuários finais e como este design converterá o código .NET. O exemplo a seguir mostra como usar uma 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 do tipo converte 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"
Observe os seguintes pontos:
O tipo do Regex padrão representa o tipo RegexTyped parametrizado.
O construtor RegexTyped resulta em uma chamada para o construtor Regex, passando o argumento estático de tipo para o padrão.
Os resultados do método Match são representados pelo tipo padrão Match .
Cada grupo nomeado resulta em uma propriedade fornecida, e acessar a propriedade resulta em um uso de um marcador na coleção Groups de uma correspondência.
O código a seguir é o núcleo da lógica para implementar um provedor desses, e este exemplo omite a adição de todos os membros do tipo fornecido. Para obter informações sobre cada membro adicionado, consulte a seção apropriada mais tarde neste tópico. Para o código completo, baixar o exemplo Bloco de F# 3,0 sobre ao site do 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 ()
Observe os seguintes pontos:
O provedor do tipo leva dois parâmetros estáticos: pattern, que é obrigatório, e options, que são opcionais (pois um valor padrão é fornecido).
Depois que os argumentos estáticos são fornecidos, você cria uma instância de expressão regular. Essa instância lança uma exceção se o Regex é incorreto, e esse erro será relatado aos usuários.
Dentro do retorno de chamada de DefineStaticParameters, você define o tipo que será retornado após os argumentos serem fornecidos.
Esse código define HideObjectMethods como verdadeiro de modo que a experiência do IntelliSense permaneça eficiente. Esse atributo causa Equals, GetHashCode, Finalize, e membros de GetType a serem suprimidos das listas IntelliSense para um objeto fornecido.
Você usa obj como o tipo base do método, mas você usará um objeto Regex como representação de tempo de execução desse tipo, como mostra o exemplo a seguir.
A chamada para o construtor do Regex gera 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. Esta exceção permite que as expressões regulares sejam validadas sem executar um aplicativo.
O tipo definido acima não é útil porque ainda não contém nenhum método ou propriedade significativo. Primeiro, adicione um método estático 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
O código anterior define um método IsMatch, que recebe uma cadeia de caracteres como entrada e retorna bool. A única parte é complicada o uso do argumento args dentro da definição de InvokeCode. Nesse exemplo, args é uma lista de citações que representa os argumentos para este método. Se o método é um método de instância, o primeiro argumento representa o argumento de this. No entanto, para um método estático, todos os argumentos são apenas os argumentos explícitos para o método. Observe que o valor do tipo citado deve corresponder ao tipo de retorno especificado (neste caso, bool). Também observe que esse código usa o método AddXmlDoc para certificar-se que o método fornecido também tem a documentação útil, que você pode fornecer com o IntelliSense.
Em seguida, adicione um método de correspondência de instância. No entanto, esse método deve retornar um valor de um tipo fornecido de Match de modo que os grupos podem ser acessados em uma forma fortemente tipada. Assim, você primeiro declara o tipo de Match. Uma vez que isso depende do tipo padrão que foi fornecido como um argumento estático, esse tipo deve ser aninhado dentro da definição de tipo com parâmetros:
let matchTy = ProvidedTypeDefinition(
"MatchType",
baseType = Some baseTy,
HideObjectMethods = true)
ty.AddMember matchTy
Você então adiciona uma propriedade para o tipo de correspondência de cada grupo. Em tempo de execução, uma correspondência é representado como um valor de Match , então a citaçã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(sprintf @"Gets the ""%s"" group from this match" group)
matchTy.AddMember prop
Novamente, observe que você está adicionando a documentação XML para a propriedade fornecida. Também observe que uma propriedade pode ser lida se uma função GetterCode for fornecida, e a propriedade pode ser gravada se uma função SetterCode for fornecida, assim que a propriedade resultante é somente leitura.
Agora você pode criar um método de instância que retorna um valor desse tipo de 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
Já que você está criando um método de instância, args.[0] representa a instância de RegexTyped em que o método está sendo chamado, e args.[1] é o argumento de entrada.
Finalmente, forneça um construtor de modo que as 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 Regex .NET padrão como exemplo, que está disponível comercialmente novamente a um objeto porque obj é exclusão do tipo fornecido. Com essa alteração, o uso da API de exemplo que foi especificada anteriormente no tópico funciona como 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>,
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 ()
Lições chave
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 no seu valor.
Um provedor de tipo que é suportado por dados locais
Frequentemente você pode desejar provedores de tipo para apresentar as APIs não apenas com base em parâmetros estáticos mas também em informações de sistemas locais ou remotos. Esta seção discute os provedores de tipo que são baseados em dados locais, como arquivos de dados local.
Provedor simples de arquivo CSV
Como um exemplo simples, considere um provedor de tipo para acessar dados científicos no formato Valores Separados por Vírgulas (CSV, em inglês). Esta seção pressupõe que os arquivos CSV contêm uma linha de cabeçalho seguida de 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 propriedade Distance do tipo float<meter> e uma propriedade Time do tipo float<second>. Para simplificar, as seguintes suposições são feitas:
Os nomes de cabeçalho não têm unidades ou têm a forma “Nome (unidade)" e não contém vírgulas.
As unidades são todas as unidades do Sistema Internacional (SI) como define o módulo Microsoft.FSharp.Data.UnitSystems.SI.UnitNames Module (F#).
As unidades são todas simples (por exemplo, metro) em vez do composto (por exemplo, metros/segundo).
Todas as colunas contêm dados de ponto flutuante.
Um provedor mais completo afrouxaria essas limitações.
Novamente a primeira etapa é considerar como a API deve ser. Dado um arquivo de info.csv com o conteúdo da tabela anterior (no formato separados por vírgulas), os usuários do provedor devem ser capaz de gravar o código que lembra o exemplo a seguir:
let info = new MiniCsv<"info.csv">()
for row in info.Data do
let time = row.Time
printfn "%f" (float time)
Nesse caso, o compilador deve converter essas chamadas em algo parecido com o seguinte exemplo:
let info = new MiniCsvFile("info.csv")
for row in info.Data do
let (time:float) = row.[1]
printfn "%f" (float time)
A conversão ideal vai requerer que o provedor do tipo defina um tipo real CsvFile no assembly do provedor do tipo. Provedores de tipo geralmente contam com 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 float[] como o tipo apagado para uma linha. O compilador irá manipular colunas diferentes contendo tipos diferentes de medida. por exemplo, a primeira coluna em nosso exemplo tem o tipo float<meter> e a segunda tem float<second>. No entanto, a representação apagada pode permanece bastante simples.
O código a seguir mostra o núcleo de 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 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])
Observe os seguintes pontos sobre a implementação:
Os construtores sobrecarregados permitem o arquivo original ou um que têm um esquema idêntico a ser lido. Esse padrão é comum quando você escreve um provedor de tipo para fontes de dados local ou remoto, e esse padrão permite que um arquivo local seja usado como o modelo para dados remotos.
Você pode usar o valor de TypeProviderConfig que é passado para o construtor do provedor de tipo para resolver nomes de arquivo relacionados.
Você pode usar o método AddDefinitionLocation para definir o local das propriedades fornecidas. Portanto, se você usar Ir Para Definição em uma propriedade fornecida, o arquivo CSV abrirá no Visual Studio.
Você pode usar o tipo ProvidedMeasureBuilder para pesquisar as unidades de SI e para gerar os tipos float<_> relevantes.
Lições chave
Esta seção explicou como criar um provedor de tipo para uma fonte de dados local com um esquema simples que está contido na fonte de dados própria.
Aprofundar
As seções a seguir incluem sugestões para um estudo adicional.
Um aspecto no código compilado para tipos apagados
Para fornecer alguma ideia de como o uso do provedor do tipo corresponde ao código que é emitida, observe a seguinte função usando 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 compilado 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 do tipo Type1 e da propriedade InstanceProperty foram apagadas, deixando somente operações nos tipos de tempo de execução envolvidas.
Design e convenções de nomenclatura para provedores de tipo
Observe as seguintes convenções para criar provedores de tipo.
Provedores para protocolos de conectividade
Geralmente, os nomes de DLL de provedor para protocolos de conectividade de dados e de serviço, como OData ou conexões SQL, devem terminar com TypeProvider ou TypeProviders. Por exemplo, use um nome da DLL que se assemelha com a seguinte cadeia de caracteres:
Fabrikam.Management.BasicTypeProviders.dll
Certifique-se de que os tipos fornecidos são membros do namespace correspondente e indica o protocolo de conectividade que você implementou:
Fabrikam.Management.BasicTypeProviders.WmiConnection<…> Fabrikam.Management.BasicTypeProviders.DataProtocolConnection<…>
Provedores de utilitários para a codificação geral
Para um provedor de tipo de utilidade como o para expressões regulares, o provedor de tipo pode ser parte de uma biblioteca de 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 .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 cancelar o sufixo de TypeProvider e usar convenções normais para a nomeação .NET:
#r "Fabrikam.Data.Freebase.dll" let data = Fabrikam.Data.Freebase.Astronomy.Asteroids
Para obter mais informações, consulte a convenção de design de GetConnection que é descrita posteriormente neste tópico.
Padrão para criar 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 de GetConnection
A maioria dos provedores de tipo devem ser escritos para usar o padrão de GetConnection que é 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 tipo que é suportado por dados e serviços remotos, você deve considerar um intervalo de problemas que são inerentes à programação conectada. Esses problemas incluem as seguintes considerações:
mapeamento de esquema
atualização ao vivo e invalidação na presença de alteração de esquema
cache de esquema
implementações assíncronas de operações de acesso a dados
uso a consultas, incluindo consultas LINQ
credenciais e autenticação
Este tópico não explora esses problemas mais profundamente.
Técnicas adicionais de design
Quando você escrever seus próprios provedores de tipo, convém usar as seguintes técnicas adicionais.
Criando os tipos e membros sob demanda
O ProvidedType API 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, ByRef e ponteiro
Você faz os membros fornecidos (cujas assinaturas incluem tipos de matriz, tipos byref e instanciações de tipos genéricos) usando MakeArrayTypenormal, MakePointerType, e MakeGenericType em qualquer instância de System.Type, incluindo ProvidedTypeDefinitions.
Fornecendo a unidade de medida de anotações
O ProvidedTypes API 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 do projeto Local ou escrita Local
Para cada instância de um provedor de tipo pode ser determinado um valor de TypeProviderConfig durante a compilação. Este valor contém a “pasta 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.
Invalidação
Provedores podem gerar sinais de invalidação para notificar o serviço linguístico F# de que as suposições de esquema podem ter sido alterado. Quando a invalidação ocorre, um typecheck é refeito se o provedor está sendo hospedado no Visual Studio. Este sinal será ignorado quando o provedor está hospedado no F# interativo ou pelo compilador de (F# fsc.exe).
Informações de esquema de cache
Provedores devem geralmente armazenar em cache o acesso a informações de esquema. Os dados armazenados em cache devem ser armazenados usando um nome de arquivo que é fornecido como um parâmetro estático ou como dados do usuário. Um exemplo de cache de esquema é o parâmetro LocalSchemaFile nos provedores de tipo no assembly de FSharp.Data.TypeProviders. Na implementação de esses provedores, este 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 na rede. Para usar informações do esquema armazenado em cache, você também deve definir o parâmetro estático ForceUpdate como false. Você pode usar uma técnica semelhante para habilitar acesso a dados online e offline.
Assembly de apoio
Quando você cria um arquivo .dll ou .exe, o arquivo .dll de suporte para tipos gerados está associado estaticamente no assembly resultante. Este link é criado copiando as definições de tipo Linguagem Intermediária (IL, em inglês) e todos os recursos gerenciados do assembly de apoio no assembly final. Quando você usa F# interativo, o arquivo .dll de apoio não é copiado, mas é carregado diretamente no processo de F# interativa.
Exceções e diagnóstico dos provedores de tipo
Todos os usos de todos os membros de tipos fornecidos podem lançar exceções. Em todos os casos, se um provedor de tipo gera uma exceção, o compilador host atribui o erro a um tipo específico de provedor.
As exceções do provedor de tipo nunca devem resultar em erros do compilador interno.
Provedores de tipo não podem relatar avisos.
Quando um provedor de tipo é hospedado no compilador F#, em um ambiente de desenvolvimento de F# ou em F# interativo, todas as exceções desse provedor são capturadas. A propriedade de mensagem é sempre o texto de erro, e nenhum rastreamento de pilha aparece. Se você irá acionar uma exceção, você pode lançar os exemplos a seguir:
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 os tipos gerados, que são adicionados como definições de tipos reais .NET no programa de usuários. Você deve referenciar 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 é parte da versão do F# 3.0 limitou somente suporte para fornecer tipos gerados. As seguintes declarações devem ser verdadeiras para uma definição de tipo gerado:
IsErased deve ser definido como false.
O provedor deve ter um assembly que tenha um arquivo de suporte .dll .NET real com um arquivo .dll correspondente no disco.
Você também deve chamar ConvertToGenerated em um tipo de raiz fornecido cujos tipos aninhados formem um conjunto fechado de tipos gerados. Esta chamada emite a definição de tipo fornecido e suas definições de tipos aninhados em um assembly, e ajusta a propriedade de Assembly de todas as definições de tipo fornecidas para retornar esse assembly. O assembly é emitido somente quando a propriedade do assembly no tipo da raiz é acessada pela primeira vez. O compilador de F# host acessa essa propriedade quando processa uma declaração de tipo generative para o tipo.
Regras e restrições
Quando você escrever provedores de tipo, mantenha as seguintes regras e restrições em mente.
Tipos fornecidos devem ser acessíveis.
Todos tipos fornecidos devem ser acessíveis de tipos não aninhados. Tipos não aninhados são fornecidos na chamada ao construtor de TypeProviderForNamespaces ou em uma chamada para AddNamespace. Por exemplo, se o provedor fornece um tipo StaticClass.P : T, você deve garantir que T é um tipo não aninhado ou aninhado em um.
Por exemplo, alguns provedores têm uma classe estática tal como DataTypes que contêm esses tipos T1, T2, T3, .... Caso contrário, o erro informa que uma referência para o tipo T no assembly A foi encontrada, mas o tipo não pôde ser encontrado naquele assembly. Se esse erro aparecer, verifique que todos os seus subtipos possam ser alcançados dos tipos de provedor. Observação: Esses tipos T1, T2, T3... são referidos como tipos durante a execução. Lembre-se de colocá-los em um namespace acessível ou em um tipo pai.
Limitações do mecanismo de provedor de tipo
O mecanismo de provedor de tipo em F# tem as seguintes restrições:
A estrutura subjacente para provedores de tipo em F# não suporta tipos genéricos ou métodos genéricos fornecidos.
O mecanismo não dá suporte para tipos aninhados com parâmetros estáticos.
Limitações de código de suporte de ProvidedTypes
O código de suporte de ProvidedTypes tem as seguintes regras e restrições:
Propriedades fornecidas com getter e definidores indexados não são implementados.
Eventos fornecidos não são implementados.
Os tipos e objetos de informações fornecidos devem ser usados somente para o mecanismo de provedor de tipo em F#. Geralmente não são mais úteis como objetos de System.Type.
As compilações que você pode usar em citações que definem implementações do método têm várias limitações. Você pode referenciar o código-fonte para ProvidedTypes-Version para ver que as compilações são suportadas entre aspas.
Provedores de tipo devem gerar os assemblies que são arquivos .dll, não arquivos de saída .exe.
Dicas de desenvolvimento
Você pode localizar os seguintes dicas úteis durante o processo de desenvolvimento.
Duas instâncias de execução do Visual Studio. Você pode desenvolver o provedor de tipo em uma instância e testar o provedor no outro porque o teste IDE receberá um bloqueio no arquivo .dll que impede o provedor do tipo de ser recompilado. Portanto, você deve fechar a segunda instância do Visual Studio quando o provedor é compilado em primeiro lugar, e então você deve reabrir a segunda instância depois que o provedor for compilado.
Depurar provedores de tipo usando chamadas de fsc.exe. Você pode chamar provedores de tipo usando as ferramentas a seguir:
fsc.exe (O compilador de linha de comando de F#)
fsi.exe (O compilador interativo de F#)
devenv.exe (Visual Studio)
Você geralmente pode depurar provedores do tipo mais facilmente usando fsc.exe em um arquivo de script de teste (por exemplo, script.fsx). Você pode iniciar um depurador de um prompt de comando.
devenv /debugexe fsc.exe script.fsx
Você pode usar log de impressão de saída padrão.