Samouczek: Tworzenie dostawca typu (F#)
Mechanizm dostawcy typu w F# 3.0 jest znaczną częścią jego obsługi dla programowania bogatego w informacje.Ten samouczek, przez zaprezentowanie rozwoju kilku prostych dostawców typu, aby zilustrować podstawowe koncepcje, wyjaśnia jak stworzyć własnych dostawców typu.Aby uzyskać więcej informacji na temat mechanizmu dostawcy typu w F#, zobacz Typ dostawcy.
Język F# 3.0 zawiera kilku wbudowanych dostawców typów dla typowych biznesowych i internetowych usług związanych z danymi.Ci dostawcy typów zapewniają prosty i zwykły dostęp do relacyjnej bazy danych SQL i opartych o sieć usług OData i WSDL.Ci dostawcy obsługują także używanie zapytań LINQ języka F# dla tych źródeł danych.
W przypadku gdy jest to konieczne, można utworzyć własnych niestandardowych dostawców typów lub odwołać się do dostawców typów stworzonych przez innych.Dla przykładu, organizacja może mieć usługę danych, która zapewniające dużą i rosnącą ilość nazwanych zestawów danych, każdy z własnym stabilnym schematem danych.Można utworzyć dostawcę typów, który czyta schematy i przedstawia programiście bieżące zestawy danych w sposób mocno typizowany.
Przed rozpoczęciem:
Mechanizm dostawcy typu jest przeznaczony głównie do wstrzykiwania stabilnych danych i przestrzeni informacji usług do doświadczeń programowania F#.
Mechanizm ten nie jest przeznaczony dla wstrzykiwania przestrzeni informacji, których schemat zmienia się podczas wykonywania programu w sposób znaczący dla logiki programu.Mechanizm również nie został zaprojektowany dla metaprogramowania wewnątrz-językowego, mimo że domena zawiera pewne poprawne zastosowania.Mechanizm ten należy używać tylko w razie potrzeby i gdzie rozwój dostawcy typu daje bardzo dużą wartość.
Należy unikać pisania dostawca typu tam, gdzie schemat nie jest dostępny.Podobnie, należy unikać pisania dostawca typu tam, gdzie zwykłe (lub nawet istniejące) biblioteki .NET byłyby wystarczające.
Przed rozpoczęciem należy zadać następujące pytania:
Czy znany jest schemat źródła informacji?Jeśli tak, co jest mapowanie do F# i systemu typów .NET?
Czy można użyć istniejących (dynamicznie typowanych) API jako punktu wyjścia dla implementacji?
Czy Ty i firma posiadacie wystarczająco dużo zastosowań dostawcy typu, aby jego napisanie się opłacało?Czy normalne biblioteki .NET odpowiadałyby potrzebom?
Jak bardzo zmieniłby się schemat?
Zmieni się podczas kodowania?
Zmieni się między sesjami kodowania?
Zmieni się podczas wykonywania programu?
Dostawcy typu najlepiej nadają się do sytuacji, gdy schemat jest stabilny w czasie wykonywania i podczas okresu istnienia skompilowanego kodu.
Prosty dostawca typu
Ten przykład to Samples.HelloWorldTypeProvider w katalogu SampleProviders\ProvidersPaczki z przykładami F# 3.0 na stronie Codeplex.Dostawca udostępnia "przestrzeń typów", zawierającą 100 typów wymazanych, co ilustruje poniższy kod przy użyciu składni sygnatury F#, pomijając szczegóły wszystkich z wyjątkiem Type1.Po więcej informacji na temat typów wymazanych, zobacz Szczegóły dotyczące dostarczonych typów wymazanych później w tym temacie.
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 =
…
Zauważ, że zbiór dostarczonych typów i członków jest statycznie znany.Przykład ten nie wykorzystuje zdolności dostawców do dostarczania typów, które zależą od schematu.Implementacja dostawcy typu jest jedynie naszkicowana w poniższym kodzie, natomiast szczegóły omówione są w kolejnych rozdziałach tego tematu.
Przestroga |
---|
Może zaistnieć małe różnice w nazewnictwie pomiędzy tym kodem i próbkami online. |
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()
Aby użyć tego dostawcy, należy otworzyć osobne wystąpienie Visual Studio 2012, utworzyć skrypt F#, a następnie dodać odwołanie do dostawcy ze skryptu przy użyciu #r, co ilustruje poniższy kod:
#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
Następnie należy poszukać typów w przestrzeni nazw Samples.HelloWorldTypeProvider, którą wygenerował dostawca typu.
Przed ponownym skompilowaniem dostawcy, należy się upewnić, że wszystkie wystąpienia Visual Studio i F# Interactive, które używają biblioteki DLL dostawcy, są zamknięte.W przeciwnym razie wystąpi błąd kompilacji, ponieważ wyjściowa biblioteka DLL będzie zablokowana.
Aby debugować dostawcę przy użyciu instrukcji wypisywania, należy utworzyć skrypt, który uwidoczni problem z dostawcą, a następnie użyć następujący kod:
fsc.exe -r:bin\Debug\HelloWorldTypeProvider.dll script.fsx
Aby debugować dostawcę przy użyciu programu Visual Studio, należy otworzyć wiersz polecenia programu Visual Studio z poświadczeniami administracyjnymi i uruchomić następujące polecenie:
devenv.exe /debugexe fsc.exe -r:bin\Debug\HelloWorldTypeProvider.dll script.fsx
Alternatywnie, można otworzyć program Visual Studio, menu Debuguj, a następnie wybrać **Debuguj/Dołącz do procesu...**i dołączyć do drugiego procesu devenv, w którym edytuje się skrypt.Za pomocą tej metody, poprzez interaktywne wpisywanie wyrażeń w drugim wystąpieniu (z pełnym IntelliSense i innymi funkcjami), można łatwiej celować w określoną logikę w dostawcy typu.
Można wyłączyć debugowanie Tylko mój kod, aby zapewnić lepszą identyfikację błędów w wygenerowanym kodzie.Aby uzyskać informacje dotyczące włączania lub wyłączania tej funkcji, zobacz How to: Step Into Just My Code.Ponadto można również ustawić przechwytywanie wyjątków pierwszej szansy poprzez otwarcie menu Debuguj, a następnie wybierając Wyjątki lub używając skrótu klawiszowego Ctrl+Alt+E, aby otworzyć okno dialogowe Wyjątki.W tym oknie dialogowym pod Wyjątki CLR, zaznacz pole wyboru Zgłoszone.
Implementacja dostawcy typu
Ta sekcja instruktażu pokazuje główne części implementacji dostawcy typu.Najpierw należy zdefiniować typ dla samego niestandardowego dostawcy typu:
[<TypeProvider>]
type SampleTypeProvider(config: TypeProviderConfig) as this =
Typ ten musi być publiczny i musi być oznaczyć atrybutem TypeProvider tak, aby kompilator rozpoznał dostawcę typu, kiedy osobny projekt F# odwołuje się do zestawu, zawierający ten typ.Parametr config jest opcjonalny i jeśli jest obecny, zawiera informacje dotyczące konfiguracji kontekstowych dla wystąpienia dostawcy typu, który kompilator F# tworzy.
Następnie należy zaimplementować interfejs ITypeProvider.W tym przypadku użyj typ TypeProviderForNamespaces z API ProvidedTypes jako typu podstawowego.Pomocnik ten, może dostarczyć skończoną kolekcję przestrzeni nazw dostarczanych zachłannie, z których każda bezpośrednio zawiera skończoną liczbę stałych typów dostarczonych zachłannie.W tym kontekście dostawca gorliwie generuje typy, nawet jeśli nie są potrzebne lub stosowane.
inherit TypeProviderForNamespaces()
Następnie należy zdefiniować lokalne, prywatne wartości, które określają przestrzeń nazw dla dostarczanych typów i znaleźć sam zestaw dostawcy typu.Zestaw ten używany jest później jako logiczny typ nadrzędny typów wymazanych, które są dostarczane.
let namespaceName = "Samples.HelloWorldTypeProvider"
let thisAssembly = Assembly.GetExecutingAssembly()
Następnie należy utworzyć funkcję, aby dostarczała każdy z typów Type1...Type100.Funkcja ta omówiona jest bardziej szczegółowo w dalszej części tego tematu.
let makeOneProvidedType (n:int) = …
Następnie można wygenerować 100 typów dostarczonych:
let types = [ for i in 1 .. 100 -> makeOneProvidedType i ]
Następnie należy dodać typy jako dostarczoną przestrzeń nazw:
do this.AddNamespace(namespaceName, types)
Na koniec należy dodać atrybut zestawu, który wskazuje, że tworzona jest biblioteka DLL dostawcy typu:
[<assembly:TypeProviderAssembly>]
do()
Dostarczanie jednego typu i jego członków
Funkcja makeOneProvidedType wykonuje rzeczywistą pracę dostarczania jednego z typów.
let makeOneProvidedType (n:int) =
…
Ten krok wyjaśnia implementację tej funkcji.Najpierw należy utworzyć dostarczany typ (na przykład Type1, gdy n = 1 lub Type57, gdy 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>)
Należy zauważyć następujące punkty:
Ten typ dostarczony jest wymazany.Ponieważ wskazuje się, że typem podstawowym jest obj, w skompilowanym kodzie wystąpienia będą pojawiać się jako wartości typu obj.
Po określeniu typu niezagnieżdżonego, należy określić zestaw i przestrzeń nazw.Dla typów wymazanych, zestawem powinien być sam dostawca typów.
Następnie należy dodać dokumentację XML do typu.Dokumentacja ta jest opóźniona, czyli wyliczana na żądanie wtedy, gdy kompilator hosta potrzebuje jej.
t.AddXmlDocDelayed (fun () -> sprintf "This provided type %s" ("Type" + string n))
Następnie należy dodać statyczną właściwość dostarczoną do typu:
let staticProp = ProvidedProperty(propertyName = "StaticProperty",
propertyType = typeof<string>,
IsStatic=true,
GetterCode= (fun args -> <@@ "Hello!" @@>))
Pobieranie tej właściwości zawsze da wynika w postaci ciągu "Hello!".GetterCode dla właściwości używa cytatu F#, co reprezentuje kod, który jest generowany przez kompilator hosta dla pobieranie właściwości.Aby uzyskać więcej informacji o cytatach, zobacz Cytaty kodu (F#).
Dodaj plik XML dokumentacji do właściwości.
staticProp.AddXmlDocDelayed(fun () -> "This is a static property")
Teraz należy dołączyć właściwość dostarczoną do typu dostarczonego.Należy dołączyć członek dostarczony do jednego i tylko jednego typu.W przeciwnym razie członek nigdy nie będzie dostępny.
t.AddMember staticProp
Teraz należy utworzyć konstruktor dostarczony, który nie przyjmuje parametrów.
let ctor = ProvidedConstructor(parameters = [ ],
InvokeCode= (fun args -> <@@ "The object data" :> obj @@>))
InvokeCode dla konstruktora zwraca cytat F#, który reprezentuje kod, który generuje kompilator hosta po wywołaniu konstruktora.Na przykład można użyć następującego konstruktora:
new Type10()
Wystąpienie typu dostarczonego zostanie stworzone z danych podstawowych "The object data".Zacytowany kod obejmuje konwersję na obj, ponieważ ten typ jest wymazaniem tego typu dostarczonego (jak określono podczas deklaracji typu dostarczonego).
Należy dodać dokumentację XML do konstruktora i konstruktor dostarczony do typu dostarczonego:
ctor.AddXmlDocDelayed(fun () -> "This is a constructor")
t.AddMember ctor
Należy stworzyć drugi konstruktor dostarczony, który przyjmuje jeden parametr:
let ctor2 =
ProvidedConstructor(parameters = [ ProvidedParameter("data",typeof<string>) ],
InvokeCode= (fun args -> <@@ (%%(args.[0]) : string) :> obj @@>))
InvokeCode dla konstruktora ponownie zwraca cytat F#, który reprezentuje kod wygenerowany przez kompilator hosta dla wywołania metody.Na przykład można użyć następującego konstruktora:
new Type10("ten")
Wystąpienie typu dostarczonego jest stworzone z danymi podstawowymi "ten".Być może zauważyłeś już, że funkcja InvokeCode zwraca cytat.Wejście do tej funkcji to lista wyrażeń, jeden na parametr konstruktora.W takim przypadku wyrażenie, które reprezentuje wartość pojedynczego parametru, jest dostępne w args.[0].Kod dla wywołania do konstruktora wymusza traktowanie wartości zwracanej jako wymazanego typu obj.Po dodaniu drugiego konstruktora dostarczonego do typu można utworzyć właściwość wystąpienia dostarczonego:
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
Pobieranie tej właściwości będzie zwracać długość ciągu, który jest reprezentacją obiektu.Właściwość GetterCode zwraca cytat F#, który określa kod, który generuje kompilator hosta, aby pobrać właściwość.Podobnie jak InvokeCode, funkcja GetterCode zwraca cytat.Kompilator hosta wywołuje tę funkcję z listą argumentów.W tym przypadku, argumenty obejmują tylko pojedyncze wyrażenie, które reprezentuje wystąpienie, na którym jest wywoływana metoda pobierająca, do której można uzyskać dostęp za pomocą args.[0]. Implementacja GetterCode następnie splata w wynik cytat na wymazanym typie obj i wykorzystuje rzutowanie, aby spełnić wymagania mechanizmów kompilatora do kontroli typów, aby obiekt był ciągiem.Następna część makeOneProvidedType zapewnia wystąpienie metody z jednym parametrem.
let instanceMeth =
ProvidedMethod(methodName = "InstanceMethod",
parameters = [ProvidedParameter("x",typeof<int>)],
returnType = typeof<char>,
InvokeCode = (fun args ->
<@@ ((%%(args.[0]) : obj) :?> string).Chars(%%(args.[1]) : int) @@>))
instanceMeth.AddXmlDocDelayed(fun () -> "This is an instance method")
// Add the instance method to the type.
t.AddMember instanceMeth
Wreszcie należy utworzyć zagnieżdżony typ, który zawierający 100 zagnieżdżonych właściwości.Tworzenie zagnieżdżone typu i jego właściwości jest opóźnione, to znaczy, wyliczane na żądanie.
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
Szczegóły dotyczące wymazanych typów dostarczonych
Przykład w tej sekcji zapewnia jedynie wymazane typy dostarczone, które są szczególnie przydatne w następujących sytuacjach:
Podczas pisania dostawcy dla przestrzeni informacyjnej, która zawiera jedynie dane i metody.
Podczas pisania dostawcy, gdzie dokładne semantyki o typie w trakcie wykonywania programu nie są krytyczne dla praktycznego wykorzystania przestrzeni informacyjnej.
Podczas pisania dostawcy dla przestrzeni informacyjnej, tak dużej i połączonej, że nie jest technicznie możliwe generowanie rzeczywistych typów .NET dla przestrzeni informacyjnej.
W tym przykładzie każdy typu dostarczony jest wymazany do typu obj i wszystkich zastosowań tego typu będą wyświetlane jako typ obj w kodzie skompilowanym.W rzeczywistości źródłowe obiekty w tych przykładach to ciągi, ale typ będzie widoczny jako Object w skompilowanym kodzie .NET.Jak z wszystkimi zastosowaniami wymazania typu, można użyć jawnego pakowania, rozpakowywania i rzutowania, aby odwrócić typy wymazane.W takim przypadku może wystąpić wyjątek rzutowania, który nie jest prawidłowy, kiedy obiekt zostanie użyty.Środowisko uruchomieniowe dostawcy może definiować własne prywatne reprezentacje typów, aby pomóc w ochronie przeciwko fałszywym reprezentacjom.Nie można zdefiniować wymazanych typów w samym F#.Jedynie typy dostarczone mogą być wymazane.Należy zrozumieć następstwa zarówno praktyczne i semantycznych albo używania wymazanych typów dostawcą typu lub dostawcy, który zawiera typy.Wymazany typ nie ma rzeczywistego typu .NET.Dlatego nie można wykonać dokładne refleksji nad typem, a można odwrócić wymazane typy, jeśli używasz rzutowania w czasie wykonania i innych technik, które polegają na dokładnych semantykach o typie w trakcie wykonywania programu.Odwracanie wymazanych typów często skutkuje w wyjątkach rzutowania typu w czasie wykonywania.
Wybieranie reprezentacji dla wymazanych typów dostarczanych
Dla niektórych zastosowań wymazanych typów dostarczanych reprezentacja nie jest wymagana.Na przykład wymazany typ dostarczony może zawierać tylko statyczne właściwości i członków, ale żadnych konstruktorów i metod lub właściwości zwracają wystąpienie typu.Jeśli można osiągnąć wystąpienia wymazanego typu dostarczonego, należy wziąć pod uwagę następujące kwestie:
Co jest wymazaniem typu dostarczonego?
Wymazanie typu dostarczonego jest tym jak ten typ wygląda w skompilowanym kodzie .NET.
Wymazanie typu dostarczonego klasy jest zawsze pierwszym nie wymazane typu podstawowego w łańcuchu dziedziczenia typu.
Wymazanie dostarczonego typu interfejsu wymazanego jest zawsze Object.
Co to są reprezentacje typu dostarczonego?
- Zbiór możliwych obiektów dla wymazanego typu dostarczonego są nazywane jego reprezentacjami.W przykładzie w tym dokumencie reprezentacje wszystkich wymazanych typów dostarczonych Type1..Type100 są zawsze obiektami typu ciąg.
Wszystkie oświadczenia typu dostarczonego muszą być zgodne z wymazaniem typu dostarczonego.(W przeciwnym razie kompilator F# da błąd przy użyciu dostawcy typu albo zostanie wygenerowany nie weryfikowalny kodu .NET, który nie jest prawidłowy.Dostawca typu nie jest prawidłowy, jeśli zwraca kod, który daje reprezentację, który nie jest prawidłowa).
Można wybrać reprezentacje dla obiektów dostarczonych przy użyciu jednej z następujących metod, z których obie są bardzo popularne:
Jeśli po prostu dostarcza się otokę o jednoznacznie określonym typie nad istniejącym typem .NET, często warto wymazać typ do tego typu, użyć wystąpienia tego typu jako reprezentacji lub zastosować oba sposoby.Takie podejście jest odpowiednie, gdy większość istniejących metod w tym typie nadal ma sens podczas używania wersji z jednoznacznie określonym typem.
Jeśli chcesz utworzyć API, które różni się znacząco od wszelkich istniejących API .NET, warto utworzyć typy środowiska uruchomieniowego, które będą wymazaniem typu i reprezentacją dla typów dostarczonych.
Przykład w tym dokumencie używa ciągów jako reprezentacji obiektów dostarczonych.Często może być właściwe wykorzystanie innych obiektów dla reprezentacji.Na przykład można użyć słownika jako zestawu właściwości:
ProvidedConstructor(parameters = [],
InvokeCode= (fun args -> <@@ (new Dictionary<string,obj>()) :> obj @@>))
Alternatywnie, można zdefiniować typ w dostawcy typu, który będzie użyty w trakcie wykonania, aby uformować reprezentację, razem z co najmniej jedną operacją środowiska uruchomieniowego:
type DataObject() =
let data = Dictionary<string,obj>()
member x.RuntimeOperation() = data.Count
Członkowie dostarczenie mogą wtedy utworzyć wystąpienia tego typu obiektu:
ProvidedConstructor(parameters = [],
InvokeCode= (fun args -> <@@ (new DataObject()) :> obj @@>))
W takim przypadku można (opcjonalnie) użyć tego typu jako wymazania przez określenie tego typu jako baseType przy konstruowaniu ProvidedTypeDefinition:
ProvidedTypeDefinition(…, baseType = Some typeof<DataObject> )
…
ProvidedConstructor(…, InvokeCode = (fun args -> <@@ new DataObject() @@>), …)
Lekcje kluczowe
W poprzedniej sekcji objaśniono, jak utworzyć prostego wymazującego dostawcę typu, który dostarcza zakres typów, właściwości i metod.W tej sekcji również wyjaśniono pojęcie wymazania typu, łącznie z pewnymi zaletami i wadami dostarczania typów wymazanych od dostawcy typu i omówiono reprezentacje typów wymazanych.
Dostawca typu, który używa parametrów statycznych
Możliwość sparametryzowania dostawców typu przez dane statyczne umożliwia wiele interesujących scenariusze, nawet w przypadkach, gdy dostawca nie wymaga dostępu do żadnych danych lokalnych lub zdalnych.W tej sekcji dowiesz się o niektórych podstawowych technikach składania takiego dostawcy w całość.
Dostawca typu sprawdzania wyrażenia regularnego
Przypuśćmy, że należy zaimplementować dostawcę typu dla wyrażeń regularnych, który otacza biblioteki .NET Regex, w interfejsie, który zapewnia następujące gwarancje kompilacji:
Weryfikowanie, czy wyrażenie regularne jest prawidłowy.
Dostarczanie nazwanych właściwości na dopasowanie, które są oparte na dowolnych nazwach grup w wyrażeniu regularnym.
W tej sekcji przedstawiono sposób, jak używać dostawców typu, aby stworzyć typ RegExProviderType, który wzorzec wyrażenia regularnego parametryzuje, aby dostarczyć te świadczenia.Kompilator zgłosi błąd jeśli podany wzorzec nie jest prawidłowy, a dostawca typu może wyodrębnić grupy z wzorca, dzięki czemu można uzyskać do nich dostęp za pomocą nazwanych właściwości w dopasowaniach.Podczas projektowania dostawcy typu, należy rozważyć jak jego wystawione API powinno wyglądać dla użytkowników końcowych i jak ten projekt przetłumaczyć na kod .NET.Poniższy przykład przedstawia w jaki sposób należy używać takie API, aby uzyskać składniki kodu kierunkowego:
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"
Poniższy przykład pokazuje, jak dostawca typu tłumaczy te wywołania:
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"
Zauważ następujące punkty:
Standardowy typ Regex reprezentuje sparametryzowany typ RegexTyped.
Konstruktor RegexTyped powoduje wywołanie konstruktora Regex, przekazując statyczny argument typu dla wzorca.
Wyniki metody Match są reprezentowane przez standardowy typ Match.
Każda nazwana grupa skutkuje w właściwości dostarczonej, a dostęp do właściwości powoduje użycie indeksator na pasującej kolekcji Groups.
Poniższy kod jest rdzeniem logiki do implementacji takiego dostawcy. W tym przykładzie pominięto dodanie wszystkich członków do typu dostarczonego.Aby uzyskać informację na temat każdego dodanego członka, zobacz odpowiednią sekcję w dalszej części tego tematu.Aby uzyskać pełny kod, pobierz przykład z Paczki próbek F# 3.0 na stronie internetowej 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 ()
Zauważ następujące punkty:
Dostawca typu przyjmuje dwa parametry statyczne: pattern, który jest obowiązkowy oraz options, który jest opcjonalny (ponieważ zapewniona jest wartość domyślna).
Po tym, jak statyczne argumenty są dostarczane, należy stworzyć wystąpienie wyrażenia regularnego.To wystąpienie zgłosi wyjątek, jeśli wyrażenie regularne jest nieprawidłowe, a błąd ten zostanie zgłoszony do użytkowników.
W obrębie wywołania zwrotnego DefineStaticParameters, definiuje się typ, który będzie zwrócony po tym, jak argumenty są dostarczane.
Ten kod ustawia HideObjectMethods na true tak, aby doświadczenia IntelliSense pozostały usprawnione.Ten atrybut powoduje, że członkowie Equals, GetHashCode, Finalize, i GetType zostają pominięci z list IntelliSense dla obiektu dostarczonego.
Używa się obj jako typu podstawowego metody, ale należy użyć obiektu Regex jako reprezentacja środowiska uruchomieniowego tego typu, jak pokazuje następny przykład.
Wywołanie do konstruktora Regex zgłasza ArgumentException kiedy wyrażenie regularne nie jest prawidłowe.Kompilator przechwytuje ten wyjątek i zgłasza komunikat o błędzie użytkownikowi w czasie kompilacji lub w edytorze Visual Studio.Wyjątek ten umożliwia sprawdzenie wyrażeń regularnych bez uruchamiania aplikacji.
Typ zdefiniowany powyżej nie jeszcze przydatny, ponieważ nie zawiera on żadnych istotnych metod lub właściwości.Najpierw należy dodać statyczną metodę 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
Poprzedni kod definiuje metodę IsMatch, która przyjmuje ciąg na wejściu i zwraca bool.Jedyna trudność polega na wykorzystaniu argumentu args w obrębie definicji InvokeCode.W tym przykładzie args jest listą cytatów, które reprezentują argumenty dla tej metody.Jeśli metoda jest metodą wystąpienia, pierwszy argument reprezentuje argument this.Jednak, dla metody statycznej, argumenty są wszystkie jedynie jawnymi argumentami dla metody.Zauważ, że typ wartości cytowanych powinien odpowiadać określonemu typowi zwracanemu (w tym przypadku bool).Ponadto należy zauważyć, że używa kodu ten używa metody AddXmlDoc, aby upewnić się, że dostarczona metoda ma także użyteczną dokumentacją, która może być dostarczona za pomocą technologii IntelliSense.
Następnie należy dodać wystąpienie metody Match.Jednakże metoda ta powinna zwrócić wartość typu dostarczonego Match, dzięki czemu grupy mogą być dostępne w sposób jednoznacznie określonego typie.W ten sposób, należy najpierw zadeklarować typ Match.Ponieważ typ ten zależy od wzorca, który został dostarczony jako argument statycznych, typ ten musi być zagnieżdżony w sparametryzowanej definicji typu:
let matchTy = ProvidedTypeDefinition(
"MatchType",
baseType = Some baseTy,
HideObjectMethods = true)
ty.AddMember matchTy
Następnie należy dodać jedną właściwość do typu Match dla każdej grupy.W czasie wykonywania, dopasowanie jest reprezentowane jako wartość Match, więc cytat, który definiuje właściwości musi używać indeksowanej właściwości Groups, aby uzyskać odpowiednie grupy.
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
Ponownie należy zauważyć, że dodawana jest dokumentacja XML do dostarczonej właściwości.Ponadto należy zauważyć, że właściwość można być odczytana, jeśli dostarczona jest funkcja GetterCode, właściwość może być zapisana, jeśli dostarczona jest funkcja SetterCode, a właściwość wynikowa jest tylko do odczytu.
Teraz można utworzyć wystąpienie metody, która zwraca wartość typu 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
Ponieważ tworzona jest metoda wystąpienia, args.[0] reprezentuje wystąpienie RegexTyped, na którym metoda jest wywoływana, a args.[1] jest argumentem wejściowym.
Wreszcie, należy dostarczyć konstruktor, dzięki któremu wystąpienia typu dostarczonego mogą być utworzone.
let ctor = ProvidedConstructor(
parameters = [],
InvokeCode = fun args -> <@@ Regex(pattern, options) :> obj @@>)
ctor.AddXmlDoc("Initializes a regular expression instance.")
ty.AddMember ctor
Konstruktor wymazuje jedynie stworzenie standardowego wystąpienia .NET Regex, które jest ponownie pakowane do obiektu, ponieważ obj jest wymazaniem typu dostarczonego.Z tą zmianą, przykładowe użycie API określone wcześniej w temacie działa zgodnie z oczekiwaniami.Poniższy kod jest pełny i ostateczny:
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 ()
Lekcje kluczowe
W tej sekcji wyjaśniono, jak utworzyć dostawce typu, który działa na swoich statycznych parametrach.Dostawca sprawdza statyczny parametr i dostarcza operacje na podstawie jego wartości.
Dostawca typu, który jest wspierany przez dane lokalne
Często może zajść potrzeba, aby dostawca typu prezentował API oparte nie tylko na statycznych parametrach, ale też na informacjach z lokalnych lub zdalnych systemów.W tej sekcji omówiono dostawców typu, którzy oparci są o dane lokalne, takie jak pliki danych lokalnych.
Prosty dostawca plików CSV
Dla prostego przykładu, rozważmy dostawcę typu, który uzyskuje dostęp do danych naukowych w formacie z wartościami rozdzielonymi przecinkami (CSV).W tej sekcji zakładamy, że pliki CSV zawierają wiersz nagłówka, a następnie dane zmiennoprzecinkowe, jak pokazano w poniższej tabeli:
Odległość (metry) |
Czas (sekundy) |
---|---|
50.0 |
3.7 |
100.0 |
5.2 |
150.0 |
6.4 |
W tej sekcji przedstawiono jak dostarczyć typ, który można wykorzystać do pobrania wierszy właściwością Distance typu float<meter> i właściwością Time typu float<second>.Dla uproszczenia, następujące założenia są przyjęte::
Nazwy nagłówków są bez jednostek lub mają postać "Nazwa (jednostka)" i nie mogą zawierać przecinków.
Wszystkie jednostki są z układu SI jak definiuje Moduł Microsoft.FSharp.Data.UnitSystems.SI.UnitNames (F#).
Wszystkie jednostki są proste (na przykład meter), a nie złożone (na przykład metry na sekundę).
Wszystkie kolumny zawierają dane zmiennoprzecinkowe.
Bardziej skomplikowany dostawca byłby mniej restrykcyjny.
Ponownie, pierwszy krok to rozważenie jak powinno wyglądać API.Biorąc pod uwagę info.csv pliku z zawartością z poprzedniej tabeli (w formacie danych rozdzielanych przecinkami), użytkownicy dostawcy powinny móc napisać kod, który przypomina poniższy przykład:
let info = new MiniCsv<"info.csv">()
for row in info.Data do
let time = row.Time
printfn "%f" (float time)
W tym przypadku kompilator należy przekonwertować te wywołania na coś, jak w następującym przykładzie:
let info = new MiniCsvFile("info.csv")
for row in info.Data do
let (time:float) = row.[1]
printfn "%f" (float time)
Optymalne tłumaczenie będzie wymagać od dostawcy typu, aby zdefiniował rzeczywisty typ CsvFile w zestawie dostawcy typu.Dostawcy typu często polegają na kilku typach pomocniczych i metodach do owijania ważnej logiki.Ponieważ miary są wymazywane w czasie wykonywania, można użyć float[] jako typu wymazanego dla wiersza.Kompilator będzie traktować różne kolumny jako mające miary różnych typów.Na przykład, pierwsza kolumna w naszym przykładzie ma typ float<meter>, a dla druga float<second>.Jednak, reprezentacja wymazana można nadal pozostać dość prosta.
Poniższy kod przedstawia rdzeń implementacji.
// 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])
Zauważ następujące punkty dotyczące implementacji:
Konstruktory przeciążone umożliwiają odczytanie oryginalnego pliku lub tego, który ma identyczny schemat.Wzorzec ten jest wspólny, podczas zapisu dostawcy typu dla lokalnych lub zdalnych źródeł zdalnych. Wzorzec ten pozwala, aby plik lokalny był użyty jako szablon dla danych zdalnych.
Można użyć wartości TypeProviderConfig, która jest przekazywana do konstruktora dostawcy typu, aby rozwiązać względne nazwy plików.
Można użyć metody AddDefinitionLocation, aby zdefiniować lokalizację właściwości dostarczonej.Dlatego jeśli używasz Przejdź do definicji na właściwości dostarczonej, plik CSV zostanie otwarty w programie Visual Studio.
Można użyć typu ProvidedMeasureBuilder do wyszukiwania jednostek SI oraz do generowania odpowiednich typów float<_>.
Lekcje kluczowe
W tej sekcji wyjaśniono jak utworzyć dostawcę typu dla lokalnego źródła danych z prostego schematu, który jest zawarty w samym źródle danych.
Idąc dalej
Poniższe sekcje zawierają sugestie dotyczące dalszych badań.
Spojrzenie na skompilowany kod dla typów wymazanych
Aby nabyć pewne pojęcie jak użycie dostawcy typu odpowiada kodowi, który jest emitowany, należy się przyjrzeć następującej funkcji, poprzez wykorzystanie HelloWorldTypeProvider, użytego wcześniej w tym temacie.
let function1 () =
let obj1 = Samples.HelloWorldTypeProvider.Type1("some data")
obj1.InstanceProperty
Oto obraz kodu wynikowego, który został zdekompilowany za pomocą ildasm.exe:
.class public abstract auto ansi sealed Module1
extends [mscorlib]System.Object
{
.custom instance void [FSharp.Core]Microsoft.FSharp.Core.CompilationMappingAtt
ribute::.ctor(valuetype [FSharp.Core]Microsoft.FSharp.Core.SourceConstructFlags)
= ( 01 00 07 00 00 00 00 00 )
.method public static int32 function1() cil managed
{
// Code size 24 (0x18)
.maxstack 3
.locals init ([0] object obj1)
IL_0000: nop
IL_0001: ldstr "some data"
IL_0006: unbox.any [mscorlib]System.Object
IL_000b: stloc.0
IL_000c: ldloc.0
IL_000d: call !!0 [FSharp.Core_2]Microsoft.FSharp.Core.LanguagePrimit
ives/IntrinsicFunctions::UnboxGeneric<string>(object)
IL_0012: callvirt instance int32 [mscorlib_3]System.String::get_Length()
IL_0017: ret
} // end of method Module1::function1
} // end of class Module1
Tak jak pokazano w przykładzie, wszystkie wzmianki typu Type1 i właściwości InstanceProperty zostały usunięte, pozostawiając zaangażowane jedynie operacje na typach środowiska uruchomieniowego.
Projektowanie i konwencje nazewnictwa dla dostawców typu
Przestrzegaj następujących konwencji podczas tworzenia dostawcy typu.
Dostawcy dla protokołów łączności
Ogólnie rzecz biorąc, nazwy większości dostawcy bibliotek DLL dla danych i usługi protokołów łączności, takich jak połączenia OData lub SQL należy kończyć przez TypeProvider lub TypeProviders.Na przykład, można użyć nazwy biblioteki DLL, która przypomina następujący ciąg:
Fabrikam.Management.BasicTypeProviders.dll
Należy zapewnić, że typy dostarczane są członkami odpowiadającej przestrzeni nazw i wskazują zaimplementowany protokół komunikacyjny:
Fabrikam.Management.BasicTypeProviders.WmiConnection<…> Fabrikam.Management.BasicTypeProviders.DataProtocolConnection<…>
Dostawcy użytkowi dla ogólnego kodowania
Dla użytkowego dostawcy typu, jak dla wyrażeń regularnych, dostawca typu może być część biblioteki podstawowej, jak pokazano w następującym przykładzie:
#r "Fabrikam.Core.Text.Utilities.dll"
W takim przypadku, typu dostarczony wydaje się być we właściwym punkcie zgodnie z normalnymi konwencjami projektu .NET:
open Fabrikam.Core.Text.RegexTyped let regex = new RegexTyped<"a+b+a+b+">()
Pojedyncze źródła danych
Niektórzy dostawcy typu łączą się z pojedynczym, dedykowanym źródłem danych i zapewniają jedynie dane.W takim przypadku należy upuścić sufiks TypeProvider i użyć normalnej konwencje nazewnictwa .NET:
#r "Fabrikam.Data.Freebase.dll" let data = Fabrikam.Data.Freebase.Astronomy.Asteroids
Aby uzyskać więcej informacji, zobacz konwencję projekt GetConnection, która opisana jest w dalszej części tego tematu.
Wzorce projektowe dla dostawcy typu
W poniższych sekcjach opisano wzorce projektowe, które można używać podczas tworzenia typu dostawcy.
Wzorzec projektowy GetConnection
Większość dostawców typu powinna być zapisywane, aby używać wzorca GetConnection, który jest używany przez dostawców typu w FSharp.Data.TypeProviders.dll, co ilustruje poniższy przykład:
#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
Dostawcy typu wspierani przez zdalne dane i usługi
Przed utworzeniem dostawcy typu, który jest wspierany przez zdalne dane i usługi, należy wziąć pod uwagę zakres zagadnień, które są nieodłączne z programowaniem połączonym.Zagadnienia te obejmują następujące kwestie:
mapowanie schematu
przeżycie i unieważnienie w obecności zmiany schematu
buforowanie schematu
implementacje asynchronicznych operacji dostępu do danych
obsługa kwerend, w tym zapytań LINQ
poświadczenia i uwierzytelnianie
Ten temat nie bada dalej tych zagadnień.
Dodatkowe techniki tworzenia
Podczas pisania własnych dostawców typu, można stosować następujące dodatkowe techniki.
Tworzenie typów i członków na żądanie
API ProvidedType ma opóźnioną wersję AddMember.
type ProvidedType = member AddMemberDelayed : (unit -> MemberInfo) -> unit member AddMembersDelayed : (unit -> MemberInfo list) -> unit
Wersje te są używane do tworzenia przestrzeni typów na żądanie.
Dostarczanie typów tablicowych, przez referencję i wskaźników
Członków dostarczonych (których podpisy obejmuje typy tablicowe, typy przez referencję (byref) i konkretyzacje typów ogólnych) można stworzyć przy użyciu normalnych MakeArrayType, MakePointerType, i MakeGenericType na dowolnym wystąpieniu System.Type, łącznie z ProvidedTypeDefinitions.
Dostarczanie adnotacji jednostek miary
API ProvidedTypes dostarcza pomocników dla dostarczanie adnotacji miar.Na przykład, aby dostarczyć typ float<kg>, należy użyć następującego kodu:
let measures = ProvidedMeasureBuilder.Default let kg = measures.SI "kilogram" let m = measures.SI "meter" let float_kg = measures.AnnotateType(typeof<float>,[kg])
Aby dostarczyć typu Nullable<decimal<kg/m^2>>, należy użyć następującego kodu:
let kgpm2 = measures.Ratio(kg, measures.Square m) let dkgpm2 = measures.AnnotateType(typeof<decimal>,[kgpm2]) let nullableDecimal_kgpm2 = typedefof<System.Nullable<_>>.MakeGenericType [|dkgpm2 |]
Dostęp do zasobów lokalnego projektu lub lokalnych skryptów
Każdemu wystąpieniu dostawcy typu można dać wartość TypeProviderConfig podczas konstrukcji.Ta wartość zawiera "resolution folder" dla dostawcy (czyli folder projektu dla kompilacji lub katalog, który zawiera skrypt), listę zestawów, do których występują odwołania i inne informacje.
Unieważnienie
Dostawcy mogą zgłosić sygnały unieważnienia, aby powiadomić usługi języka F#, że założenia schematu mogły się zmienić.W przypadku wystąpienia unieważnienia, sprawdzenie typów jest ponownie wykonane jeśli dostawca jest obsługiwany w Visual Studio.Sygnał ten zostanie zignorowany, gdy dostawca jest obsługiwany w F# Interactive lub przez kompilator F# (fsc.exe).
Buforowanie informacji o schemacie
Dostawcy często muszą buforować dostęp do informacji o schemacie.Buforowane dane powinny być przechowywane przy użyciu nazwy pliku, któremu nadano statyczny parametr lub jako dane użytkownika.Przykładem buforowania schematu jest parametr LocalSchemaFile w dostawcach typu w zestawie FSharp.Data.TypeProviders.W implementacji takich dostawców, ten statyczny parametr kieruje dostawcę typu, aby wykorzystywał schematu informacji w określonym pliku lokalnego, a nie wykorzystywał źródła danych za pośrednictwem sieci.Aby użyć zbuforowanych informacji o schemacie, należy także ustawić parametr statyczny ForceUpdate na false.Podobnej technika można użyć, aby udostępnić dostęp do danych w trybie online i offline.
Zestaw pomocniczy
Podczas kompilowania pliku .dll lub .exe, pomocniczy plik .dll dla wygenerowanych typów jest statycznie łączony z zestawem wynikowym.Łącze to jest tworzone przez kopiowanie definicji typów języka pośredniego (IL) i wszystkich zasobów zarządzanych z zestawu pomocniczego do zestawu końcowego.Używając F# Interactive, pliku pomocniczy .dll nie jest kopiowany i zamiast tego jest ładowany bezpośrednio do procesu F# Interactive.
Wyjątki i diagnostyki dla dostawców typu
Wszystkie zastosowania wszystkich członków z typów dostarczonych mogą zgłaszać wyjątki.We wszystkich przypadkach, jeśli dostawca typu zgłosi wyjątek, kompilator hosta przypisuje błąd do określonego dostawcy typu.
Wyjątki dostawca typu nigdy nie powinny skutkować w wewnętrznych błędach kompilatora.
Dostawcy typu nie może raportować ostrzeżeń.
Kiedy dostawca typu jest obsługiwany w kompilatorze F#, środowisku programowania F# lub F# Interactive, wszystkie wyjątki od tego dostawcy są przechwytywane.Właściwością Komunikat jest zawsze tekst błędu, ale bez śladu stosu.Jeśli zachodzi potrzeba zgłoszenia wyjątku, można zgłosić następujące przykłady:
Dostarczanie typów generowanych
Dotychczas w tym dokumencie wyjaśniono jak dostarczać typy wymazane.Można też użyć mechanizmu dostawcy typu w F#, aby dostarczyć typy generowane, które są dodawane jako rzeczywiste definicje typów .NET do programu użytkowników.Należy odnosić się do dostarczonych typów generowanych przy użyciu definicji typu.
open Microsoft.FSharp.TypeProviders
type Service = ODataService<" http://services.odata.org/Northwind/Northwind.svc/">
Kod pomocnik ProvidedTypes-0.2, który jest częścią wydania F# 3.0, ma jedynie ograniczone wsparcie dla dostarczania typów generowanych.Poniższe instrukcje muszą być spełnione dla definicji typu generowanego:
IsErased musi być ustawione na false.
Dostawca musi mieć zestawu, który posiada faktyczny plik pomocniczy .NET .dll z odpowiadającym plikiem .dll na dysku.
Należy także wywołać ConvertToGenerated na głównym dostarczonym typie, którego zagnieżdżone typy formują zbiór zamkniętych typów generowanych.To wywołanie emituje daną definicję dostarczonego typu i jego definicje typów zagnieżdżonych w zestaw i dostosowuje właściwość Assembly wszystkich definicji typów dostarczonych, aby zwracały ten zestaw.Zestaw jest jest emitowany tylko wtedy, gdy dostęp do właściwości Assembly na typie głównym jest uzyskiwany po raz pierwszy.Kompilator F# hosta uzyskuje dostęp do tej właściwości podczas przetwarzania wytwórczych deklaracji typu dla typu.
Zasady i ograniczenia
Podczas pisania dostawcy typu, należy pamiętać o następujących zasadach i ograniczeniach.
Typy dostarczone muszą być osiągalne.
Wszystkie typy dostarczane muszą być osiągalne z typów niezagnieżdżonych typów.Niezagnieżdżone typy są przekazane w wywołaniu do konstruktora TypeProviderForNamespaces lub wywołaniu do AddNamespace.Na przykład, jeśli dostawca dostarcza typ StaticClass.P : T, należy się upewnić, że T jest typem niezagnieżdżonym lub zagnieżdżonym pod jednym.
Na przykład, niektórzy dostawcy mają klasy statyczne takie jak DataTypes, które zawierają typy T1, T2, T3, ....W przeciwnym razie błąd mówi, że znaleziono odwołanie do typu T w zestawie A, ale nie można odnaleźć typu w tym zestawie.Jeśli pojawia się taki błąd, należy sprawdzić, czy wszystkie podtypy są osiągalne z typów dostawcy.Uwaga: Typy T1, T2, T3... są określone jako typy w locie.Pamiętaj, aby umieścić je w dostępnej przestrzeni nazw lub typie nadrzędnym.
Ograniczenia mechanizmu dostawcy typu
Mechanizm dostawcy typu F# ma następujące ograniczenia:
Podstawowa infrastruktura dla dostawców typu w F# nie obsługuje dostarczonych typów ogólnych lub dostarczonych metod ogólnych.
Mechanizm ten nie obsługuje zagnieżdżonych typów z parametrami statycznymi.
Ograniczenia kodu wsparcia ProvidedTypes
Kod wsparcia ProvidedTypes ma następujące zasady i ograniczenia:
Właściwości dostarczone z indeksowanymi metodami pobierającymi i ustawiającymi nie są implementowane.
Zdarzenia dostarczone nie są implementowane.
Typy dostarczone i obiekty informacji należy stosować tylko dla mechanizmu dostawcy typu w F#.Nie są bardziej ogólnie użyteczne jak obiekty System.Type.
Konstrukcje, które można używać w cytatach, które definiują implementacje metod, mają kilka ograniczeń.Można się odwołać do kodu źródłowego dla ProvidedTypes-Version, aby wyświetlić konstrukcje, które są obsługiwane w cytatach.
Typ dostawcy należy wygenerować zestawy danych wyjściowych, które są plikami .dll, nie przez pliki .exe.
Porady dotyczące programowania
Poniższe porady mogą być przydatne podczas procesu programowania.
Warto uruchomić dwa wystąpienia programu Visual Studio. W jednym wystąpieniu można programować dostawcę typu, a testować go w drugiej, ponieważ IDE testowe zablokuje plik .dll, co uniemożliwi ponowne skompilowanie dostawcy typu.Dlatego należy zamknąć drugie wystąpienie programu Visual Studio, podczas kiedy dostawca jest kompilowany w pierwszej instancji, a następnie ponownie otworzyć drugą instancję, po skompilowaniu dostawcy.
Warto debugować dostawcę typu używając wywołań fsc.exe. Dostawcę typu można wywołać za pomocą następujących narzędzi:
fsc.exe (Kompilator F# wiersza polecenia)
fsi.exe (Kompilator F# Interactive)
devenv.exe (Visual Studio)
Dostawcę typu można często najłatwiej debugować przy użyciu fsc.exe w testowym pliku skryptu (na przykład script.fsx).Debugera można uruchomić z wiersza polecenia.
devenv /debugexe fsc.exe script.fsx
Można użyć rejestrowania przez wypisanie do stdout.