Share via


Tutorial: Crear un proveedor de tipo (F#)

El mecanismo proveedor de tipos en F# 3.0 es una parte importante de apoyo parala programación con información completa.Este tutorial explica cómo crear sus propios proveedores de tipos, a la vez que le guía en el desarrollo de varios proveedores de tipos simples para ilustrar los conceptos básicos.Para obtener más información sobre el mecanismo proveedor de tipos de F#, consulte Proveedores de tipo.

F# 3.0 contiene varios proveedores de tipos integrados que se utilizan habitualmente para servicios de datos y para datos empresariales en Internet .Estos proveedores de tipos proporcionan acceso simple y regular a bases de datos relacionales SQL y servicios basados en redes de OData y WSDL.Estos proveedores también admiten el uso de consultas LINQ de F# con estos orígenes de datos.

En caso necesario, se pueden crear proveedores de tipos personalizados o se pueden referenciar proveedores de tipos creados por otros.Por ejemplo, la organización podría tener un servicio de datos que proporcionara un gran número creciente de conjuntos de datos con nombre, cada uno con su propio esquema de datos estable.Se puede crear un proveedor de tipos que lea los esquemas y presente los conjuntos de datos actuales al desarrollador de una manera fuertemente tipada.

Antes de empezar

El mecanismo proveedor de tipos está diseñado principalmente para insertar espacios de datos e información de servicios estables en la programación de F#.

Este mecanismo no está diseñado para insertar espacios de información cuyo esquema cambie durante la ejecución del programa de forma relevante para la lógica del programa.Además, el mecanismo no es seguro para la meta-programación del intra-lenguaje, aunque ese dominio contenga algunas aplicaciones válidas.Debe utilizar este mecanismo sólo en caso necesario y cuando el desarrollo de un proveedor de tipos produzca un valor muy alto.

Se debería evitar escribir un proveedor de tipos cuando no está disponible un esquema.Igualmente, se debe evitar escribir un proveedor de tipos cuando una biblioteca normal (o incluso una existente) de .NET sería suficiente.

Antes de comenzar, debería hacerse las siguientes preguntas:

  • ¿Tiene un esquema para su fuente de información?Si lo tiene, ¿cuál es la correspondencia entre los sistemas de tipos de F# y .NET?

  • ¿Puede utilizar una API (dinámicamente tipada) existente como punto de partida para su implementación?

  • ¿Usted y su organización tendrán suficientes aplicaciones del proveedor de tipos para hacer que valga la pena escribirlo?¿Una biblioteca normal de .NET cubriría sus necesidades?

  • ¿Cuánto cambiará el esquema?

  • ¿Cambiará durante la codificación?

  • ¿Cambiará entre sesiones de codificación?

  • ¿Cambiará durante la ejecución del programa?

Los proveedores de tipos son más adecuados en situaciones en las que el esquema en tiempo de ejecución es estable y durante el tiempo de vida del código compilado.

Un proveedor de tipos simple

En el sitio web de Codeplex, dentro del directorio Paquete de ejemplos de F# 3.0SampleProviders\Providers se puede encontrar este ejemplo llamado Samples.HelloWorldTypeProvider.El proveedor hace disponible un “espacio de tipos” que contiene 100 tipos desactivados, como muestra el código siguiente mediante sintaxis de la firma de F# y la omisión de los detalles de todos excepto de Type1.Para obtener más información sobre tipos desactivados, consulte Detalles sobre tipos proporcionados borrados más adelante en este tema.

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 se conoce el conjunto de tipos y miembros proporcionados estáticamente.Este ejemplo no aprovecha la capacidad de los proveedores para proporcionar los tipos que dependen de un esquema.La implementación del proveedor de tipos se muestra en el código siguiente y sus detalles se cubren en secciones posteriores de este tema.

Nota de precauciónPrecaución

Puede haber algunas pequeñas diferencias entre los nombres de este código y los ejemplos en línea.

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 utilizar este proveedor, abra una instancia independiente de Visual Studio 2012, cree un script de F# y agregue una referencia al proveedor del script utilizando #r como se muestra en el código siguiente:

#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

Después busque los tipos en el espacio de nombres Samples.HelloWorldTypeProvider que el proveedor de tipos generó.

Antes de volver a compilar el proveedor, asegúrese de que se han cerrado todas las instancias de Visual Studio y de F# interactive que están utilizando el proveedor DLL.Si no, aparece un error de compilación porque la salida DLL se bloquea.

Para depurar este proveedor mediante declaraciones de impresión, cree un script que exponga un problema con el proveedor y después utilice el siguiente código:

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

Para depurar este proveedor mediante Visual Studio, abra el símbolo del sistema de Visual Studio con credenciales administrativas y ejecute el siguiente comando:

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

Como alternativa, abra Visual Studio, abra el menú Depurar, elija Depurar/Asociar para procesar… y adjunte a otro proceso devenv en donde se está editando el script.Con este método, le resultará más fácil centrarse en la lógica particular del proveedor de tipos escribiendo interactivamente expresiones en la segunda instancia (con IntelliSense completo y otras características).

Puede deshabilitar la depuración de "sólo mi código" para identificar mejor los errores en el código generado.Para obtener información sobre cómo habilitar o deshabilitar esta característica, consulte Cómo: Ir a sólo mi código.Además, también puede establecer la captura de excepciones de primera oportunidad que se detecten abriendo el menú Depurar y eligiendo Excepciones o presionando las teclas Ctrl+Alt+E para abrir el cuadro de diálogo Excepciones.En el cuadro de diálogo, en Excepciones de Common Language Runtime, active la casilla de verificación Lanzada.

Hh361034.collapse_all(es-es,VS.110).gifImplementación del proveedor de tipos

En esta sección se muestran los puntos principales de la implementación del proveedor de tipos.Primero, se define el tipo del proveedor de tipos personalizado:

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

Este tipo debe ser público y debe ser marcado con el atributo TypeProvider de modo que el compilador reconozca el proveedor de tipos cuando un proyecto independiente de F# haga referencia al ensamblado que contiene el tipo.El parámetro config es opcional y, si está presente, contiene información de configuración contextual para la instancia del proveedor de tipos que crea el compilador de F#.

A continuación, implemente la interfaz ITypeProvider .En este caso, puede utilizar el tipo TypeProviderForNamespaces de la API ProvidedTypes como tipo base.Este tipo de aplicación auxiliar puede proporcionar una colección finita de espacios de nombres ya proporcionados anticipadamente, y cada uno contiene directamente un número finito de tipos fijos que ya se han proporcionado.En este contexto, el proveedor genera anticipadamente tipos aunque no sean necesarios o utilizados.

inherit TypeProviderForNamespaces()

A continuación, defina los valores locales privados que especifica el espacio de nombres para los tipos especificados y busque el propio ensamblado de proveedor de tipos.Este ensamblado se utiliza más adelante como el tipo primario lógico de los tipos borrados proporcionados.

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

A continuación, cree una función para proporcionar cada uno de los tipos Type1… Type100.Esta función se explica con más detalle más adelante en este tema.

let makeOneProvidedType (n:int) = …

A continuación, genere los 100 tipos especificados:

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

Ahora, agregue los tipos como un espacio de nombres proporcionado:

do this.AddNamespace(namespaceName, types)

Finalmente, agregue un atributo de ensamblado que indique que se está creando un DLL de proveedores de tipos:

[<assembly:TypeProviderAssembly>] 
do()

Hh361034.collapse_all(es-es,VS.110).gifProporcionar un tipo y sus miembros

La función makeOneProvidedType hace el trabajo real de proporcionar uno de los tipos.

let makeOneProvidedType (n:int) = 
        …

Este paso explica la implementación de esta función.Primero, cree el tipo proporcionado (por ejemplo, Type1, cuando n = 1, o Type57, cuando 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>)



Se deberían tener en cuenta los siguientes puntos:

  • Este tipo proporcionado se borra.Dado que indica que el tipo base es obj, las instancias aparecerán como valores de tipo obj en el código compilado.

  • Cuando se especifica un tipo no anidado, se debe especificar también el ensamblado y el espacio de nombres.Para tipos borrados, el ensamblado deberá ser el propio ensamblado de proveedores de tipos.

A continuación, agregue la documentación XML al tipo.Esta documentación se retrasa, es decir, se computa a petición si el compilador host la necesita.

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

Luego se agrega una propiedad estática proporcionada al tipo:

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

Al obtener esta propiedad siempre se evalúa como la cadena “Hello!”.El GetterCode utiliza para la propiedad un comentario de F#, que representa el código que el compilador host genera para obtener la propiedad.Para obtener más información acerca de comentarios, consulte Comentarios de código (F#).

Agregue la documentación XML a la propiedad.

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

Ahora adjunte la propiedad proporcionada al tipo proporcionado.Debe asociar un miembro proporcionado a un único tipo.De otro modo, el miembro nunca será accesible.

t.AddMember staticProp

Ahora cree un constructor proporcionado que no reciba ningún parámetro.

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

El InvokeCode para el constructor devuelve una expresión de F#, que representa el código que el compilador host genera cuando se llama al constructor.Por ejemplo, puede usar el siguiente constructor:

new Type10()

Una instancia del tipo proporcionado se creará con los datos subyacentes “datos del objeto”.El código entrecomillado incluye una conversión a obj porque ese tipo es el elemento eliminado de dicho tipo (como se especificó cuando se declaró el tipo dado).

Agregue la documentación XML al constructor y agregue el constructor proporcionado al tipo dado:

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

t.AddMember ctor

Cree un segundo constructor proporcionado que tome un parámetro:

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

El InvokeCode para el constructor devuelve de nuevo un comentario de F#, que representa el código que el compilador host generó para una llamada al método.Por ejemplo, puede usar el siguiente constructor:

     new Type10("ten")

Una instancia del tipo proporcionado se crea con los datos subyacentes “diez”.Es posible que haya notado que la función InvokeCode devuelve un comentario.La entrada a esta función es una lista de expresiones, una por parámetro del constructor.En este caso, una expresión que representa el valor del parámetro único está disponible en args.[0].El código para una llamada al constructor fuerza el valor devuelto al tipo borrado obj.Después de agregar el segundo constructor proporcionado, cree una propiedad instancia dada:

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

Al obtener esta propiedad se devuelve la longitud de la cadena, que es el objeto de representación.La propiedad GetterCode devuelve un comentario de F# que especifica el código que el compilador host genera para obtener la propiedad.Como InvokeCode, la función GetterCode devuelve un comentario.El compilador host llama a esta función con una lista de argumentos.En este caso, los argumentos incluyen únicamente la expresión que representa la instancia desde la que se llama al método, a la cual puede tener acceso mediante args.[0]. Seguidamente, la implementación de GetterCode se divide entre la parte entrecomillada resultante en el tipo borrado obj y se utiliza una conversión para satisfacer el mecanismo del compilador que verifica aquellos tipos en los que el objeto es una cadena.La parte siguiente de makeOneProvidedType proporciona un método de instancia con un 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, cree un tipo anidado que contenga 100 propiedades anidadas.La creación de este tipo anidado y sus propiedades se retrasa, es decir, se computa a petición.

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

Hh361034.collapse_all(es-es,VS.110).gifDetalles sobre los tipos proporcionados borrados

El ejemplo de esta sección sólo proporciona tipos proporcionados borrados, que son especialmente útiles en las siguientes situaciones:

  • Cuando se escribe un proveedor para un espacio de información que sólo contiene datos y métodos.

  • Cuando se escribe un proveedor en el que no es fundamental la semántica precisa en tiempo de ejecución para el uso práctico del espacio de información.

  • Cuando se escribe un proveedor para un espacio de información que es tan grande y está tan interconectado que no es posible técnicamente generar tipos reales de .NET para el espacio de información.

En este ejemplo, se borra cada tipo proporcionado para el tipo obj y todas las aplicaciones del tipo aparecen como tipo obj en el código compilado.De hecho, los objetos subyacentes en estos ejemplos son cadenas, pero el tipo aparecerá como Object en el código compilado de .NET.Como con todas las aplicaciones de eliniación de tipos, se puede utilizar la conversión boxing explícita, conversión unboxing y conversión para tipos borrados.En este caso, una excepción de conversión no válida puede producirse cuando se utiliza el objeto.El tiempo de ejecución de un proveedor puede definir su propio tipo de representación privado para ayudar a protegerse contra representaciones falsas.No se pueden definir tipos borrados en F#.Solo los tipos provistos pueden ser borrados.Se deben entender las ramificaciones, tanto prácticas como semánticas, de utilizar bien los tipos borrados para el proveedor de tipos o bien un proveedor que proporcione tipos borrados.Un tipo borrado no tiene un tipo real de .NET.Por consiguiente, no se puede hacer una reflexión precisa sobre el tipo y se podrían subvertir los tipos borrados si se utilizan conversiones en tiempo de ejecución y otras técnicas que dependen de semánticas exactas en tiempo de ejecución.La subversión de tipos borrados frecuentemente da lugar a excepciones de conversión de tipos en tiempo de ejecución.

Hh361034.collapse_all(es-es,VS.110).gifElegir representaciones para tipos proporcionados borrados

Para algunas aplicaciones de tipos proporcionados borrados, no se requiere ninguna representación.Por ejemplo, el tipo proporcionado borrado podría contener únicamente propiedades y miembros estáticos, y ningún constructor ni ningún método o propiedad devolverían una instancia del tipo.Si se pueden obtener instancias de un tipo proporcionado borrado, considere las siguientes preguntas:

  • ¿Cuál es el borrado de un tipo proporcionado?

    • El borrado de un tipo proporcionado es cómo aparece el tipo en el código compilado de .NET.

    • El borrado de una clase de un tipo proporcionado borrado siempre es el primer tipo base no borrado en la cadena de herencia del tipo.

    • El borrado de un tipo de interfaz borrado proporcionado es siempre Object.

  • ¿Cuáles son las representaciones de un tipo proporcionado?

    • Al conjunto de los objetos posibles para un tipo proporcionado borrado se les denomina representaciones.En el ejemplo de este documento, las representaciones de todos los tipos proporcionados borrados Type1..Type100 son siempre objetos de cadenas de caracteres.

Todas las representaciones de un tipo proporcionado deben ser compatibles con el borrado del tipo proporcionado.(De lo contrario, el compilador de F# generará un error para un uso del proveedor de tipo o se generará código no comprobable de .NET que no es válido.Un proveedor de tipos no es válido si el código de retorno proporciona una representación no válida.)

Se puede elegir una representación para objetos proporcionados mediante uno de los enfoques siguientes, los cuales son muy comunes:

  • Si se proporciona simplemente un contenedor fuertemente tipado sobre un tipo existente de .NET, tiene sentido que su tipo borre a ese tipo, utilice instancias de ese tipo como representaciones, o ambas.Este enfoque es adecuado cuando la mayoría de los métodos existentes en ese tipo tienen sentido al utilizar la versión fuertemente tipada.

  • Si se desea crear una API que difiera mucho de cualquier API existente de .NET, tiene sentido crear tipos en tiempo de ejecución que representen el borrado y la representación para los tipos dados.

El ejemplo de este documento utiliza cadenas como representaciones de los objetos proporcionados.Con frecuencia, puede ser adecuado utilizar otros objetos para las representaciones.Por ejemplo, se puede utilizar un diccionario como contenedor de propiedades:

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

Como alternativa, se puede definir un tipo en el proveedor de tipos que se usará en tiempo de ejecución para formar la representación, junto con una o más operaciones en tiempo de ejecución:

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

Entonces los miembros provistos podrán construir instancias de este tipo de objeto:

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

En este caso, se puede (opcionalmente) utilizar este tipo como el borrado de tipos especificando este tipo como el baseType al construir la ProvidedTypeDefinition:

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

Lecciones clave

La sección anterior explicó cómo crear un proveedor de tipos borrados simple que proporcione un rango de tipos, propiedades y métodos.En esta sección se explica también el concepto de borrado de tipos, incluidas algunas de las ventajas y desventajas de proporcionar tipos borrados desde un proveedor de tipos y se habla sobre las representaciones de tipos borrados.

Un proveedor de tipos que utiliza parámetros estáticos

La capacidad de parametrizar los proveedores de tipos mediante datos estáticos permite muchos escenarios interesantes, incluso en casos cuando el proveedor no necesita tener acceso a ningún dato local o remoto.En esta sección, se aprenderán algunas técnicas básicas para construir tal proveedor.

Hh361034.collapse_all(es-es,VS.110).gifProveedor de tipos comprobados Regex

Imagine que desea implementar un proveedor de tipos para expresiones regulares que se ajuste a las bibliotecas de .NET Regex en una interfaz que proporciona las siguientes garantías en tiempo de compilación:

  • Comprobar si una expresión regular es válida.

  • Proporcionar propiedades nombradas en las coincidencias basadas en cualquier nombre de grupo en la expresión regular.

En esta sección se muestra cómo utilizar los proveedores de tipos para crear un RegExProviderType que el patrón de expresiones regulares parametriza para proporcionar estas ventajas.El compilador notificará un error si el patrón proporcionado no es válido y el proveedor de tipos puede extraer los grupos del patrón de modo que se puedan acceder mediante propiedades nombradas en coincidencias.Cuando se diseña un proveedor de tipos, se debería considerar cómo su API expuesta debería verse para los usuarios finales y cómo este diseño será traducido a código .NET.El ejemplo siguiente muestra cómo utilizar una API como ésta para obtener los componentes del 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"

El ejemplo siguiente muestra cómo convierte el proveedor de tipos estas llamadas:

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"

Tenga en cuenta los siguientes puntos:

  • El tipo estándar Regex representa el tipo parametrizado RegexTyped.

  • El constructor RegexTyped produce una llamada al constructor Regex, pasando el argumento de tipo estático para el patrón.

  • Los resultados del método Match se representan mediante el tipo estándar Match.

  • Cada resultado de grupo nombrado produce una propiedad proporcionada, y el acceso a la propiedad produce el uso de un indizador en la colección Groups de coincidencias.

El siguiente código es la base de la lógica para implementar este tipo de proveedor y este ejemplo omite la adición de todos los miembros al tipo proporcionado.Para obtener información sobre cada miembro agregado, consulte más adelante la sección correspondiente a este tema.Para el código completo, descargue el ejemplo Paquete de ejemplo de F# 3.0 en el sitio Web de 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 ()

Tenga en cuenta los siguientes puntos:

  • El proveedor de tipos toma dos parámetros estáticos: pattern, que es obligatorio y options, que son opcionales (porque se proporciona un valor predeterminado).

  • Después de proporcionar que los argumentos estáticos, se crea una instancia de la expresión regular.Esta instancia lanza una excepción si el Regex está formado mal y se notifica este error a los usuarios.

  • Dentro de la llamada de retorno DefineStaticParameters, se define el tipo que se devuelve cuando se dan los argumentos.

  • Este código establece HideObjectMethods en true para que el manejo de IntelliSense siga siendo sencillo.Este atributo hace que los miembros Equals, GetHashCode, Finalize y GetType se supriman de listas de IntelliSense para un objeto proporcionado.

  • Se utiliza obj como el tipo base del método, pero se utilizará un objeto Regex como representación en tiempo de ejecución de este tipo, como muestra el ejemplo siguiente.

  • La llamada al constructor Regex lanza una excepción ArgumentException cuando una expresión regular no es válida.El compilador detecta esta excepción y envía un mensaje de error al usuario en tiempo de compilación o en el editor de Visual Studio.Esta excepción permite que las expresiones regulares se validen sin ejecutar una aplicación.

El tipo definido anteriormente no es útil aún porque todavía no contiene ningún método o propiedad significativo.Primero, agregue un 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

El código anterior define un método IsMatch, que toma una cadena como entrada y devuelve un bool.La única parte difícil es el uso del argumento args dentro de la definición de InvokeCode.En este ejemplo, args es una lista de expresiones que representa los argumentos de este método.Si el método es un método de instancia, el primer argumento representa el argumento this.Sin embargo, para un método estático, los argumentos son simplemente los argumentos explícitos para el método.Observe que el tipo de valor mencionado debe coincidir con el tipo del valor de retorno especificado (en este caso, bool).Observe también que este código utiliza el método AddXmlDoc para asegurarse de que el método dado también tiene documentación útil, que se puede proporcionar con IntelliSense.

Luego, agregue un método Match a la instancia.Sin embargo, este método debe devolver un valor de un tipo Match proporcionado, de modo que se pueda acceder a los grupos de manera fuertemente tipada.Así, primero se declara el tipo Match.Como este tipo depende del patrón que se proporcionó como argumento estático, este tipo debe estar anidado dentro de la definición de tipos parametrizada:

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

ty.AddMember matchTy

A continuación se agrega una propiedad al tipo Match para cada grupo.En tiempo de ejecución, una coincidencia se representa como un valor Match, por lo que la expresión que define la propiedad debe utilizar la propiedad indizada Groups para obtener el grupo pertinente.

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

Una vez más, observe que se está agregando la documentación XML a la propiedad proporcionada.También observe que una propiedad puede ser leída si se proporciona una función GetterCode y la propiedad se puede escribir si se proporciona una función SetterCode, por lo que la propiedad resultante es de sólo lectura.

Ahora puede crear un método de instancia que retorne un valor de este tipo 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

Como se está creando un método de instancia, args.[0] representa la instancia de RegexTyped en la que se llama al método y args.[1] es el argumento de entrada.

Finalmente, proporcione un constructor para poder crear instancias del tipo proporcionado.

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

ty.AddMember ctor

El constructor simplemente borra la creación de un Regex estándar de .NET, el cual es empaquetado de nuevo en un objeto porque obj es el borrado del tipo proporcionado.Con ese cambio, el uso de la API de ejemplo especificada previamente en el tema funciona como se esperaba.El siguiente código está completo y es 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 ()

Lecciones clave

Esta sección ha explicado cómo crear un proveedor de tipos que funcione en sus parámetros estáticos.El proveedor comprueba el parámetro estático y proporciona operaciones basadas en su valor.

Un proveedor de tipos que está respaldado por datos locales

Es frecuente que se quiera que los proveedores de tipo muestren las API basadas no sólo en parámetros estáticos sino también en información de sistemas locales o remotos.Esta sección trata de los proveedores de tipos basados en datos locales, como los archivos de datos locales.

Hh361034.collapse_all(es-es,VS.110).gifProveedor simple de archivos CSV

Como ejemplo sencillo, considere un proveedor de tipos con acceso a datos científicos en el formato de valores separados por comas (CSV).En esta sección se supone que los archivos CSV contienen una fila de encabezado seguida de datos en coma flotante, como se muestra en la tabla siguiente:

Distancia (metro)

Tiempo (segundo)

50.0

3.7

100.0

5.2

150.0

6.4

Esta sección muestra cómo proporcionar un tipo que se puede utilizar para recuperar filas con una propiedad Distance de tipo float<meter> y una propiedad Time de tipo float<second>.Para simplificar, se asume lo siguiente:

  • Los nombres de encabezado carecen de unidades o tienen el formato “Nombre (unidad)” y no contienen comas.

  • Las unidades pertenecen a las unidades del Sistema Internacional (SI) como lo define el módulo Microsoft.FSharp.Data.UnitSystems.SI.UnitNames Module (F#).

  • Las unidades son todas simples (por ejemplo, metro) en lugar de compuestas (por ejemplo, metro por segundo).

  • Todas las columnas contienen datos en coma flotante.

Un proveedor más completo aflojaría estas restricciones.

De nuevo, el primer paso es considerar qué aspecto debe tener la API.Dado un archivo de info.csv con el contenido de la tabla anterior (en formato separados por comas), los usuarios de proveedor deben poder escribir un código similar al ejemplo siguiente:

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

En este caso, el compilador debería convertir estas llamadas en algo similar al siguiente ejemplo:

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

La translación óptima requerirá el proveedor de tipos para definir un tipo CsvFile real en el ensamblado del proveedor de tipos.Los proveedores de tipos a veces se basan en algunos tipos y métodos auxiliares para envolver la lógica importante.Dado que las medidas se borran en tiempo de ejecución, se puede utilizar float[] como el tipo de borrado para una fila.El compilador tratará las vcolumnas diferentes como si tuvieran diferentes tipos de medida.Por ejemplo, la primera columna en nuestro ejemplo es de tipo float<meter> y la segunda es float<second>.Sin embargo, la representación borrada puede seguir siendo bastante simple.

En el siguiente ejemplo de código se muestra lo principal de la implementación.

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

Tenga en cuenta las siguientes observaciones sobre la implementación:

  • Los constructores sobrecargados permiten que bien el archivo original o uno que tenga un esquema idéntico se lean.Este patrón es habitual al escribir un proveedor de tipos para fuentes de datos locales o remotos y además permite el uso de un archivo local como plantilla para información remota.

    Se puede utilizar el valor TypeProviderConfig que se pasa al constructor del proveedor de tipos para resolver los nombres relativos de los archivos.

  • Se puede utilizar el método AddDefinitionLocation para definir la ubicación de las propiedades proporcionadas.Por consiguiente, si se utiliza Ir a definición en una propiedad especificada, el archivo CSV se abre en Visual Studio.

  • Se puede utilizar el tipo ProvidedMeasureBuilder para buscar las unidades del SI y generar los tipos pertinentes float<_>.

Lecciones clave

En esta sección se ha explicado cómo crear un proveedor de tipos para un origen de datos local con un esquema simple que está contenido en el mismo origen de datos.

Yendo más lejos

Las siguientes secciones incluyen sugerencias para profundizar más.

Hh361034.collapse_all(es-es,VS.110).gifUna mirada al código compilado para tipos borrados

Para saber cómo se corresponde el uso del proveedor de tipos con el código emitido, revise la siguiente función mediante HelloWorldTypeProvider, utilizado anteriormente en este tema.

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

A continuación se muestra una imagen del código resultante decompilado mediante 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 muestra el ejemplo, se han borrado todas las menciones del tipo Type1 y la propiedad InstanceProperty, quedando sólo operaciones implicadas en los tipos del tiempo de ejecución.

Hh361034.collapse_all(es-es,VS.110).gifDiseño y convenciones de nomenclatura para los proveedores de tipos

Observe las siguientes convenciones al crear proveedores de tipos.

  • Proveedores para protocolos de conectividad

    Normalmente, los nombres de los archivos DLL de proveedores para protocolos de conectividad a datos y servicios, como OData o conexiones SQL, deben finalizar en TypeProvider o TypeProviders.Por ejemplo, utilice un nombre de DLL similar a la siguiente cadena:

    Fabrikam.Management.BasicTypeProviders.dll
    

    Asegúrese de que los tipos proporcionados son miembros del espacio de nombres correspondiente e indican el protocolo de conexión que se ha implementado:

    Fabrikam.Management.BasicTypeProviders.WmiConnection<…>
    Fabrikam.Management.BasicTypeProviders.DataProtocolConnection<…>
    
  • Proveedores de servicios para codificación general

    Para un proveedor de tipos de servicio, como el de las expresiones regulares, el proveedor de tipos puede ser parte de una biblioteca base, como se muestra en el siguiente ejemplo:

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

    En este caso, el tipo proporcionado aparecería en un punto adecuado según las convenciones normales de diseño de .NET:

    open Fabrikam.Core.Text.RegexTyped
    
    let regex = new RegexTyped<"a+b+a+b+">()
    
  • Orígenes de datos singleton

    Algunos proveedores de tipos se conectan a un único origen de datos exclusivo y sólo proporcionan datos.En este caso, se debería quitar el sufijo TypeProvider y utilizar las convenciones normales de nomenclatura de .NET:

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

    Para obtener más información, vea la convención de diseño GetConnection descrita más adelante en este tema.

Hh361034.collapse_all(es-es,VS.110).gifDiseñar los modelos para los proveedores de tipos

Las secciones siguientes describen modelos de diseño que se pueden utilizar cuando se crean los proveedores de tipos.

Hh361034.collapse_all(es-es,VS.110).gifEl patrón de diseño GetConnection

La mayoría de los proveedores de tipos se deben escribir para usar el patrón GetConnection utilizado por los proveedores de tipos en FSharp.Data.TypeProviders.dll, como se muestra en el ejemplo siguiente:

#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

Hh361034.collapse_all(es-es,VS.110).gifProveedores de tipos apoyados por datos y servicios remotos

Antes de crear un proveedor de tipos respaldado por datos y servicios remotos, se debe tener en cuenta varios problemas que son inherentes a la programación conectada.Estos problemas incluyen los siguientes aspectos:

  • asignación de esquemas

  • vida de la conexión y anulación en presencia de cambio de esquema

  • almacenamiento en caché de esquemas

  • implementaciones asíncronas de operaciones de acceso a datos

  • apoyo de consultas, incluidas las consultas LINQ

  • credenciales y autenticación

Este tema no profundiza en estos problemas.

Hh361034.collapse_all(es-es,VS.110).gifTécnicas adicionales de creación

Al escribir sus proveedores de tipos propios, quizá quiera utilizar las siguientes técnicas adicionales.

  • Crear tipos y miembros bajo demanda

    La API de ProvidedType ha retrasado las versiones de AddMember.

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

    Estas versiones se utilizan para crear espacios de tipos a petición.

  • Proporcionar tipos de arreglos, ByRef y tipos de punteros

    Se crean miembros proporcionados (cuyas firmas incluyen tipos de arrays, tipos byref e instanciaciones de tipos genéricos) mediante el uso de MakeArrayType normal, MakePointerType, y MakeGenericType en cualquier instancia de System.Type, incluyendo ProvidedTypeDefinitions.

  • Proporcionar unidad de anotaciones de medidas

    La API ProvidedTypes proporciona auxiliares para proporcionar anotaciones de medidas.Por ejemplo, para proporcionar el tipo float<kg>, utilice el siguiente 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 proporcionar el tipo Nullable<decimal<kg/m^2>>, utilice el código siguiente:

    let kgpm2 = measures.Ratio(kg, measures.Square m)
    let dkgpm2 = measures.AnnotateType(typeof<decimal>,[kgpm2])
    let nullableDecimal_kgpm2 = typedefof<System.Nullable<_>>.MakeGenericType [|dkgpm2 |]
    
  • Acceder a recursos locales del proyecto o del script

    A cada instancia de un proveedor de tipos se le puede asignar un valor TypeProviderConfig durante la construcción.Este valor contiene la “carpeta de resolución” para el proveedor (es decir, la carpeta del proyecto para la compilación o el directorio que contiene un script), la lista de ensamblados a los que se hace referencia y otra información.

  • Invalidación

    Los proveedores pueden traer señales de invalidación para notificar al servicio de lenguaje de F# que las hipótesis acerca del esquema pueden haber cambiado.Cuando la invalidación aparece, se rehace una revisión de tipos si el proveedor se aloja en Visual Studio.Esta señal se omite cuando el proveedor se hospede en F# interactive o por el compilador de F# (fsc.exe).

  • Almacenar en caché información de esquema

    Los proveedores deben almacenar, con frecuencia, en memoria caché el acceso a la información de esquema.Los datos almacenados en caché deben almacenarse utilizando un nombre de archivo que se dé como parámetro estático o como datos de usuario.Un ejemplo de almacenamiento en caché de esquema es el parámetro LocalSchemaFile en los proveedores de tipos en el ensamblado FSharp.Data.TypeProviders.En la implementación de estos proveedores, este parámetro estático ordena al proveedor de tipos utilizar la información del esquema en el archivo local especificado en lugar de acceder a la fuente de datos en la red.Para utilizar información de esquema almacenada en la caché, también se debe establecer el parámetro estático ForceUpdate a false.Se puede usar una técnica similar para permitir el acceso a datos con y sin conexión.

  • Ensamblado de respaldo

    Cuando se compila un archivo .dll o .exe, el archivo de respaldo .dll para los tipos generados se vincula estáticamente al ensamblado resultante.Este vínculo se crea copiando las definiciones de tipo de (IL) de lenguaje intermedio y cualquier recurso administrado del ensamblado de respaldo al ensamblado final.Cuando se utiliza F# interactivo, el archivo de respaldo .dll no se copia y en su lugar se carga directamente en el proceso de F# interactivo.

  • Excepciones y Diagnósticos de proveedores de tipos

    Todos los usos de todos los miembros de tipos provistos pueden lanzar excepciones.En todos los casos, si un proveedor de tipos lanza una excepción, el compilador host atribuye el error a un proveedor de tipos específico.

    • Las excepciones de proveedores de tipos nunca deben producir errores internos del compilador.

    • Los proveedores de tipos no pueden notificar advertencias.

    • Cuando un proveedor de tipos se aloja en el compilador de F#, un entorno de desarrollo de F# o F# interactivo, se detectan todas las excepciones de ese proveedor.La propiedad Message es siempre el texto del error y no aparece ningún seguimiento de pila.Si se va a lanzar una excepción, se pueden lanzar los ejemplos siguientes:

Hh361034.collapse_all(es-es,VS.110).gifProporcionar tipos generados

Hasta ahora, en este documento se ha explicado cómo proporcionar tipos borrados.También se puede usar el mecanismo de proveedores de tipos en F# para proporcionar tipos generados, que se agregan como definiciones de tipo reales de .NET en el programa del usuario.Se debe hacer referencia a tipos proporcionados generados mediante una definición de tipo.

open Microsoft.FSharp.TypeProviders 

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

El código auxiliar ProvidedTypes-0.2 que forma parte de la versión F# 3.0 sólo tiene compatibilidad limitada para proporcionar tipos generados.Los enunciados siguientes deben ser verdaderos para una definición de un tipo generado:

  • IsErased debe estar establecido en false.

  • El proveedor debe tener un ensamblado que tenga un archivo .dll de respaldo real de .NET con un archivo coincidente .dll en el disco.

También se debe llamar a ConvertToGenerated en un tipo proporcionado raíz cuyos tipos anidados forman un conjunto cerrado de tipos generados.Esta llamada emite la definición de tipo proporcionado especificada y sus definiciones de tipo anidadas en un ensamblado y ajusta la propiedad Assembly de todas las definiciones de tipos provistos para devolver ese ensamblado.El ensamblado se emite sólo cuando se accede a la propiedad Assembly en el tipo raíz por primera vez.El compilador de F# del host accede a esta propiedad cuando procesa una declaración de tipos generativa para el tipo.

Reglas y Limitaciones

Al escribir proveedores de tipo, recuerde las siguiente reglas y limitaciones.

  • Los tipos provistos deben ser accesibles.

    Todos los tipos provistos deben ser accesibles desde los tipos no anidados.Los tipos no anidados son proporcionados en la llamada al constructor TypeProviderForNamespaces o en una llamada a AddNamespace.Por ejemplo, si el proveedor proporciona un tipo StaticClass.P : T, debe asegurarse de que T es de tipo no anidado o anidado bajo una clase.

    Por ejemplo, algunos proveedores tienen una clase estática como DataTypes que contienen estos tipos T1, T2, T3, ....Si no, el error indica que una referencia al tipo T en el ensamblado A fue encontrada, pero el tipo no se encuentra en ese ensamblado.Si aparece este error, compruebe que todos los subtipos pueden ser alcanzados desde los tipos de proveedor.Nota: A estos tipos T1, T2, T3... se les llama tipos simultáneos.Recuerde colocarlos en un espacio de nombres accesible o en un tipo primario.

  • Limitaciones del mecanismo proveedor de tipos

    El mecanismo proveedor de tipos en F# tiene las siguientes limitaciones:

    • La infraestructura subyacente para los proveedores escritos en F# no admite tipos genéricos provistos o métodos genéricos provistos.

    • El mecanismo no admite tipos anidados con parámetros estáticos.

  • Limitaciones del código de respaldo de los ProvidedTypes

    El código de compatibilidad de los ProvidedTypes tiene las siguientes reglas y limitaciones:

    1. Las propiedades provistas con captadores y establecedores indizados no están implementadas.

    2. Los eventos provistos no estan implementados.

    3. Los tipos provistos y los objetos de información únicamente deben utilizarse para el mecanismo proveedor de tipos en F#.Ya no son generalmente utilizables como objetos de System.Type.

    4. Las construcciones que se pueden usar en las expresiones que definen las implementaciones de los métodos tienen varias limitaciones.Se puede consultar el código fuente acerca de ProvidedTypes-Versión para ver qué construcciones se admiten en las expresiones.

  • Los proveedores de tipo deben generar ensamblados que son archivos .dll, no archivos de salida .exe.

Sugerencias de desarrollo

Se pueden encontrar las siguientes sugerencias útiles durante el proceso de desarrollo.

  • Ejecute dos instancias de Visual Studio. Se puede desarrollar el proveedor de tipos en una instancia y probar el proveedor en la otra porque el IDE de prueba bloqueará el archivo .dll que evita que el proveedor de tipos sea recompilado.Por lo tanto, se debe cerrar la segunda instancia de Visual Studio mientras se compila el proveedor en primer lugar y, después, debe volver a abrir la segunda instancia después de compilar el proveedor.

  • Depuración de proveedores de tipos mediante llamadas a fsc.exe. Se pueden invocar los proveedores de tipos mediante las siguientes herramientas:

    • fsc.exe (El compilador de línea de comandos de F#)

    • fsi.exe (El compilador interactivo de F#)

    • devenv.exe (Visual Studio)

    A veces se pueden depurar los proveedores de tipos de la manera más fácil mediante fsc.exe en un archivo de script de prueba (por ejemplo, script.fsx).Se puede iniciar un depurador desde el símbolo del sistema.

    devenv /debugexe fsc.exe script.fsx
    

    Se puede utilizar el registro de IMPR-a-stdout.

Vea también

Otros recursos

Proveedores de tipo