Catatan
Akses ke halaman ini memerlukan otorisasi. Anda dapat mencoba masuk atau mengubah direktori.
Akses ke halaman ini memerlukan otorisasi. Anda dapat mencoba mengubah direktori.
Mekanisme penyedia jenis di F# adalah bagian penting dari dukungannya untuk pemrograman informasi yang kaya. Tutorial ini menjelaskan cara membuat penyedia jenis Anda sendiri dengan memandu Anda melalui pengembangan beberapa penyedia jenis sederhana untuk mengilustrasikan konsep dasar. Untuk informasi selengkapnya tentang mekanisme penyedia jenis di F#, lihat Penyedia Tipe.
Ekosistem F# berisi berbagai penyedia jenis untuk layanan data Internet dan perusahaan yang umum digunakan. Contohnya:
FSharp.Data mencakup penyedia jenis untuk format dokumen JSON, XML, CSV, dan HTML.
SwaggerProvider mencakup dua penyedia jenis generatif yang menghasilkan model objek dan klien HTTP untuk API yang dijelaskan oleh skema OpenApi 3.0 dan Swagger 2.0.
FSharp.Data.SqlClient memiliki sekumpulan penyedia tipe untuk penyematan T-SQL yang diperiksa pada saat kompilasi di F#.
Anda dapat membuat penyedia jenis kustom, atau Anda dapat mereferensikan penyedia jenis yang telah dibuat orang lain. Misalnya, organisasi Anda dapat memiliki layanan data yang menyediakan sejumlah besar himpunan data bernama, masing-masing dengan skema data stabilnya sendiri. Anda dapat membuat type provider (penyedia tipe) yang membaca skema dan menyajikan himpunan data saat ini kepada programmer dengan cara yang ditentukan dengan ketat.
Sebelum Memulai
Mekanisme penyedia jenis terutama dirancang untuk menyuntikkan data yang stabil dan ruang informasi layanan ke dalam pengalaman pemrograman F#.
Mekanisme ini tidak dirancang untuk menyuntikkan ruang informasi yang perubahan skemanya selama eksekusi program dengan cara yang relevan dengan logika program. Selain itu, mekanisme ini tidak dirancang untuk meta-pemrograman bahasa intra, meskipun domain tersebut berisi beberapa kegunaan yang valid. Anda harus menggunakan mekanisme ini hanya jika benar-benar diperlukan dan ketika pengembangan penyedia tipe memberikan nilai yang sangat tinggi.
Anda harus menghindari penulisan penyedia jenis di mana skema tidak tersedia. Demikian juga, Anda sebaiknya menghindari menulis penyedia tipe ketika pustaka .NET biasa (atau bahkan yang sudah ada) sudah memadai.
Sebelum memulai, Anda mungkin mengajukan pertanyaan berikut:
Apakah Anda memiliki skema untuk sumber informasi Anda? Jika demikian, bagaimana pemetaannya ke dalam sistem tipe F# dan .NET?
Dapatkah Anda menggunakan API yang ada (ditik secara dinamis) sebagai titik awal untuk implementasi Anda?
Apakah Anda dan organisasi Anda memiliki cukup pemanfaatan penyedia tipe data untuk membuat penulisannya bermanfaat? Apakah pustaka .NET normal memenuhi kebutuhan Anda?
Seberapa besar perubahan pada skema Anda?
Apakah akan berubah selama pengkodan?
Apakah akan berubah di antara sesi pengkodian?
Apakah akan berubah selama eksekusi program?
Penyedia jenis paling cocok untuk situasi di mana skema stabil pada waktu proses dan selama masa pakai kode yang dikompilasi.
Penyedia Jenis Sederhana
Sampel ini adalah Samples.HelloWorldTypeProvider, mirip dengan sampel di examples direktori SDK Penyedia Jenis F#. Penyedia menyediakan "ruang jenis" yang berisi 100 jenis yang dihapus, seperti yang ditunjukkan kode berikut dengan menggunakan sintaks tanda tangan F# dan menghilangkan detail untuk semua kecuali Type1. Untuk informasi selengkapnya tentang tipe yang telah dihapus, lihat Detail Tentang Tipe yang Disediakan yang Dihapus selanjutnya dalam topik ini.
namespace Samples.HelloWorldTypeProvider
type Type1 =
/// This is a static property.
static member StaticProperty : string
/// This constructor takes no arguments.
new : unit -> Type1
/// This constructor takes one argument.
new : data:string -> Type1
/// This is an instance property.
member InstanceProperty : int
/// This is an instance method.
member InstanceMethod : x:int -> char
nested type NestedType =
/// This is StaticProperty1 on NestedType.
static member StaticProperty1 : string
…
/// This is StaticProperty100 on NestedType.
static member StaticProperty100 : string
type Type2 =
…
…
type Type100 =
…
Perhatikan bahwa kumpulan jenis dan anggota yang disediakan diketahui secara statis. Contoh ini tidak memanfaatkan kemampuan penyedia untuk menyediakan jenis yang bergantung pada skema. Implementasi penyedia jenis diuraikan dalam kode berikut, dan detailnya dibahas di bagian selanjutnya dari topik ini.
Peringatan
Mungkin ada perbedaan antara kode ini dan sampel online.
namespace Samples.FSharp.HelloWorldTypeProvider
open System
open System.Reflection
open ProviderImplementation.ProvidedTypes
open FSharp.Core.CompilerServices
open FSharp.Quotations
// This type defines the type provider. When compiled to a DLL, it can be added
// as a reference to an F# command-line compilation, script, or project.
[<TypeProvider>]
type SampleTypeProvider(config: TypeProviderConfig) as this =
// Inheriting from this type provides implementations of ITypeProvider
// in terms of the provided types below.
inherit TypeProviderForNamespaces(config)
let namespaceName = "Samples.HelloWorldTypeProvider"
let thisAssembly = Assembly.GetExecutingAssembly()
// Make one provided type, called TypeN.
let makeOneProvidedType (n:int) =
…
// Now generate 100 types
let types = [ for i in 1 .. 100 -> makeOneProvidedType i ]
// And add them to the namespace
do this.AddNamespace(namespaceName, types)
[<assembly:TypeProviderAssembly>]
do()
Untuk menggunakan penyedia ini, buka instans terpisah Visual Studio, buat skrip F#, lalu tambahkan referensi ke penyedia dari skrip Anda dengan menggunakan #r seperti yang ditunjukkan kode berikut:
#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
Kemudian cari tipe di dalam Samples.HelloWorldTypeProvider namespace yang dihasilkan penyedia tipe.
Sebelum Mengkompilasi ulang penyedia, pastikan Anda telah menutup semua instans Visual Studio dan F# Interactive yang menggunakan DLL penyedia. Jika tidak, kesalahan build akan terjadi karena DLL output akan dikunci.
Untuk men-debug penyedia ini dengan menggunakan pernyataan cetak, buat skrip yang mengekspos masalah dengan penyedia, lalu gunakan kode berikut:
fsc.exe -r:bin\Debug\HelloWorldTypeProvider.dll script.fsx
Untuk men-debug penyedia ini dengan menggunakan Visual Studio, buka Perintah Pengembang untuk Visual Studio dengan kredensial administratif, dan jalankan perintah berikut:
devenv.exe /debugexe fsc.exe -r:bin\Debug\HelloWorldTypeProvider.dll script.fsx
Sebagai alternatif, buka Visual Studio, buka menu Debug, pilih Debug/Attach to process…, dan lampirkan ke proses lain devenv tempat Anda mengedit skrip. Dengan menggunakan metode ini, Anda dapat lebih mudah menargetkan logika tertentu di penyedia jenis dengan mengetik ekspresi secara interaktif ke dalam instans kedua (dengan IntelliSense penuh dan fitur lainnya).
Anda dapat menonaktifkan penelusuran kesalahan Just My Code untuk mengidentifikasi kesalahan dalam kode yang dihasilkan dengan lebih baik. Untuk informasi tentang cara mengaktifkan atau menonaktifkan fitur ini, lihat Menavigasi melalui Kode dengan Debugger. Selain itu, Anda juga dapat mengatur penangkapan pengecualian kesempatan pertama dengan membuka menu Debug dan memilih Exceptions, atau dengan memilih tombol Ctrl+Alt+E untuk membuka kotak dialog Exceptions. Dalam kotak dialog tersebut, di bawah Common Language Runtime Exceptions, pilih kotak Thrown centang.
Implementasi Penyedia Jenis
Bagian ini memandu Anda melalui bagian utama dari implementasi penyedia tipe. Pertama, Anda menentukan jenis untuk penyedia jenis kustom itu sendiri:
[<TypeProvider>]
type SampleTypeProvider(config: TypeProviderConfig) as this =
Jenis ini harus publik, dan Anda harus menandainya dengan atribut TypeProvider sehingga pengkompilasi akan mengenali penyedia jenis ketika proyek F# terpisah mereferensikan rakitan yang berisi jenis . Parameter konfigurasi bersifat opsional, dan, jika ada, berisi informasi konfigurasi kontekstual untuk instans penyedia jenis yang dibuat pengkompilasi F#.
Selanjutnya, Anda menerapkan antarmuka ITypeProvider . Dalam hal ini, Anda menggunakan TypeProviderForNamespaces jenis dari ProvidedTypes API sebagai jenis dasar. Jenis pembantu ini dapat menyediakan kumpulan namespace yang disediakan dengan bersemangat, yang masing-masing secara langsung berisi jumlah terbatas jenis tetap yang disediakan dengan bersemangat. Dalam konteks ini, penyedia dengan bersemangat menghasilkan jenis bahkan jika tidak diperlukan atau digunakan.
inherit TypeProviderForNamespaces(config)
Selanjutnya, tentukan nilai privat lokal yang menentukan namespace untuk jenis yang disediakan, dan temukan rakitan penyedia jenis itu sendiri. Asemblasi ini nantinya digunakan sebagai tipe induk logis dari tipe yang dihapus yang tersedia.
let namespaceName = "Samples.HelloWorldTypeProvider"
let thisAssembly = Assembly.GetExecutingAssembly()
Selanjutnya, buat fungsi untuk menyediakan masing-masing jenis Type1... Jenis100. Fungsi ini dijelaskan secara lebih rinci nanti dalam topik ini.
let makeOneProvidedType (n:int) = …
Selanjutnya, hasilkan 100 jenis yang disediakan:
let types = [ for i in 1 .. 100 -> makeOneProvidedType i ]
Selanjutnya, tambahkan jenis sebagai namespace yang disediakan:
do this.AddNamespace(namespaceName, types)
Terakhir, tambahkan atribut assembly yang menunjukkan bahwa Anda membuat DLL penyedia jenis:
[<assembly:TypeProviderAssembly>]
do()
Menyediakan suatu jenis dan anggotanya
Fungsi makeOneProvidedType melakukan tugas utama untuk menyediakan salah satu jenisnya.
let makeOneProvidedType (n:int) =
…
Langkah ini menjelaskan implementasi fungsi ini. Pertama, buat jenis yang disediakan (misalnya, Type1, ketika n = 1, atau Type57, ketika 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>)
Anda harus mencatat poin-poin berikut:
Jenis yang disediakan ini dihapus. Karena Anda menunjukkan bahwa jenis dasarnya adalah
obj, instans akan muncul sebagai nilai jenis obj dalam kode yang dikompilasi.Saat Anda menentukan tipe yang tidak bersarang, Anda harus menentukan assembly dan namespace. Untuk jenis yang dihapuskan, rakitan harus menjadi rakitan dari penyedia jenis itu sendiri.
Selanjutnya, tambahkan dokumentasi XML ke jenis . Dokumentasi ini tertunda, yaitu dihitung sesuai kebutuhan jika kompilator host membutuhkannya.
t.AddXmlDocDelayed (fun () -> $"""This provided type {"Type" + string n}""")
Selanjutnya Anda menambahkan properti statis yang disediakan ke jenis :
let staticProp = ProvidedProperty(propertyName = "StaticProperty",
propertyType = typeof<string>,
isStatic = true,
getterCode = (fun args -> <@@ "Hello!" @@>))
Mendapatkan properti ini akan selalu bernilai string "Hello!".
GetterCode untuk properti menggunakan kutipan F#, yang mewakili kode yang dihasilkan pengkompilasi host untuk mendapatkan properti . Untuk informasi selengkapnya tentang kutipan, lihat Kutipan Kode (F#).
Tambahkan dokumentasi XML ke properti .
staticProp.AddXmlDocDelayed(fun () -> "This is a static property")
Sekarang hubungkan properti yang disediakan ke tipe yang ditentukan. Anda harus melampirkan anggota yang telah disediakan ke satu tipe dan hanya satu tipe. Jika tidak, anggota tidak akan pernah dapat diakses.
t.AddMember staticProp
Sekarang buat konstruktor yang disediakan yang tidak mengambil parameter.
let ctor = ProvidedConstructor(parameters = [ ],
invokeCode = (fun args -> <@@ "The object data" :> obj @@>))
InvokeCode untuk konstruktor mengembalikan kutipan F#, yang mewakili kode yang dihasilkan kompilator host saat konstruktor dipanggil. Misalnya, Anda dapat menggunakan konstruktor berikut:
new Type10()
Instans jenis yang disediakan akan dibuat dengan data yang mendasar "Data objek". Kode yang dikutip mencakup konversi ke obj karena jenis tersebut adalah penghapusan jenis yang disediakan ini (seperti yang Anda tentukan saat Anda mendeklarasikan jenis yang disediakan).
Tambahkan dokumentasi XML ke konstruktor, dan tambahkan konstruktor yang disediakan ke jenis yang disediakan:
ctor.AddXmlDocDelayed(fun () -> "This is a constructor")
t.AddMember ctor
Buat konstruktor kedua yang disediakan yang mengambil satu parameter:
let ctor2 =
ProvidedConstructor(parameters = [ ProvidedParameter("data",typeof<string>) ],
invokeCode = (fun args -> <@@ (%%(args[0]) : string) :> obj @@>))
InvokeCode untuk konstruktor mengembalikan kuotasi F#, yang merupakan representasi kode yang dihasilkan kompilator host untuk panggilan ke metode. Misalnya, Anda dapat menggunakan konstruktor berikut:
new Type10("ten")
Instance dari tipe yang diberikan dibuat dengan data dasar "sepuluh". Anda mungkin telah memperhatikan bahwa fungsi InvokeCode mengembalikan sebuah kutipan. Input ke fungsi ini adalah daftar ekspresi, satu per parameter konstruktor. Dalam hal ini, ekspresi yang mewakili nilai parameter tunggal tersedia di args[0]. Kode untuk panggilan ke konstruktor memaksa nilai pengembalian ke tipe obj yang dihapus. Setelah Anda menambahkan konstruktor kedua yang disediakan ke tipe, Anda membuat properti instans baru yang disediakan.
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
Mendapatkan properti ini akan mengembalikan panjang string, yang merupakan objek representasi. Properti GetterCode mengembalikan kutipan F# yang menentukan kode yang dihasilkan pengkompilasi host untuk mendapatkan properti . Seperti InvokeCode, fungsi GetterCode itu mengembalikan kutipan. Pengkompilasi host memanggil fungsi ini dengan daftar argumen. Dalam hal ini, argumen hanya menyertakan ekspresi tunggal yang mewakili instans tempat getter dipanggil, yang dapat Anda akses dengan menggunakan args[0]. Implementasi GetterCode kemudian menggabungkan ke dalam kutipan hasil pada tipe obj yang dihapus, dan cast digunakan untuk menyesuaikan diri dengan mekanisme kompilator untuk memeriksa jenis bahwa objek tersebut adalah string. Bagian berikutnya dari makeOneProvidedType menyediakan metode instans dengan satu parameter.
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
Terakhir, buat jenis berlapis yang berisi 100 properti berlapis. Pembuatan jenis berlapis ini dan propertinya tertunda, yaitu dihitung sesuai permintaan.
t.AddMembersDelayed(fun () ->
let nestedType = ProvidedTypeDefinition("NestedType", Some typeof<obj>)
nestedType.AddMembersDelayed (fun () ->
let staticPropsInNestedType =
[
for i in 1 .. 100 ->
let valueOfTheProperty = "I am string " + string i
let p =
ProvidedProperty(propertyName = "StaticProperty" + string i,
propertyType = typeof<string>,
isStatic = true,
getterCode= (fun args -> <@@ valueOfTheProperty @@>))
p.AddXmlDocDelayed(fun () ->
$"This is StaticProperty{i} on NestedType")
p
]
staticPropsInNestedType)
[nestedType])
Detail tentang Jenis yang Disediakan Yang Dihapus
Contoh di bagian ini hanya menyediakan jenis yang disediakan yang dihapus, yang sangat berguna dalam situasi berikut:
Ketika Anda menulis penyedia untuk ruang informasi yang hanya mengandung data dan metode.
Ketika Anda menulis suatu penyedia di mana semantik tipe runtime yang akurat tidak terlalu penting untuk pemanfaatan praktis ruang informasi.
Ketika Anda menulis penyedia untuk ruang informasi yang sangat besar dan saling terhubung sehingga tidak memungkinkan secara teknis untuk menghasilkan tipe .NET yang nyata untuk ruang informasi.
Dalam contoh ini, setiap tipe yang diberikan diubah menjadi tipe obj, dan semua penggunaan tipe akan diterjemahkan sebagai tipe obj dalam kode yang dikompilasi. Bahkan, objek yang mendasar dalam contoh ini adalah string, tetapi jenisnya akan muncul seperti System.Object dalam kode yang dikompilasi .NET. Seperti semua penggunaan penghapusan tipe, Anda dapat menggunakan pengkotakan eksplisit, pembukaan kotak, dan penerapan cast untuk mengubah jenis yang dihapus. Dalam hal ini, pengecualian konversi tipe yang tidak valid dapat terjadi saat objek digunakan. Runtime penyedia dapat menentukan jenis representasi privatnya sendiri untuk membantu melindungi dari representasi palsu. Anda tidak dapat menentukan jenis yang dihapus di F# itu sendiri. Hanya jenis yang disediakan yang dapat dihapus. Anda perlu memahami konsekuensi, baik praktis maupun semantik, dari menggunakan tipe yang dihapus untuk penyedia tipe Anda atau penyedia yang menyediakan tipe yang dihapus. Tipe yang terhapus tidak memiliki tipe .NET yang sebenarnya. Oleh karena itu, Anda tidak dapat melakukan refleksi yang akurat atas jenis , dan Anda mungkin menumbangkan jenis yang dihapus jika Anda menggunakan cast runtime dan teknik lain yang mengandalkan semantik jenis runtime yang tepat. Pengubahan jenis yang dihapus sering kali menghasilkan pengecualian konversi tipe saat waktu eksekusi.
Memilih Representasi untuk Jenis yang Disediakan yang Dihapus
Untuk beberapa penggunaan jenis yang disediakan yang dihapus, tidak diperlukan representasi. Misalnya, jenis yang disediakan yang dihapus mungkin hanya berisi properti statis dan anggota, tidak ada konstruktor, dan tidak ada metode atau properti yang mengembalikan instans dari jenis tersebut. Jika Anda dapat menjangkau instans jenis yang disediakan yang dihapus, Anda harus mempertimbangkan pertanyaan berikut:
Apa penghapusan tipe yang diberikan?
Penghapusan jenis yang disediakan adalah bagaimana jenis muncul dalam kode .NET yang dikompilasi.
Penghapusan jenis kelas yang dihapus yang disediakan selalu merupakan jenis dasar pertama yang tidak dihapus dalam rantai pewarisan jenis.
Penghapusan jenis antarmuka yang dihapus yang disediakan selalu
System.Object.
Apa saja representasi dari tipe yang diberikan?
- Kumpulan objek yang mungkin untuk jenis yang disediakan yang dihapus disebut representasinya. Dalam contoh dalam dokumen ini, representasi dari semua jenis
Type1..Type100yang disediakan yang dihapus selalu merupakan objek string.
Semua representasi dari tipe yang disediakan harus kompatibel dengan penghilangan sifat tipe tersebut. (Jika tidak, pengkompilasi F# akan memberikan kesalahan untuk penggunaan penyedia jenis, atau kode .NET yang tidak dapat diverifikasi yang tidak valid akan dihasilkan. Penyedia jenis tidak valid jika mengembalikan kode yang memberikan representasi yang tidak valid.)
Anda dapat memilih representasi untuk objek yang disediakan dengan menggunakan salah satu pendekatan berikut, yang keduanya sangat umum:
Jika Anda hanya menyediakan pembungkus yang diketik dengan kuat atas jenis .NET yang ada, seringkali masuk akal bagi jenis Anda untuk menghapus jenis tersebut, menggunakan instans jenis tersebut sebagai representasi, atau keduanya. ** Pendekatan ini sesuai ketika sebagian besar metode yang ada pada jenis tersebut tetap relevan saat menggunakan versi yang bertipe kuat.
Jika Anda ingin membuat API yang sangat berbeda dari API .NET yang ada, masuk akal untuk membuat jenis runtime yang akan berfungsi sebagai penghapusan tipe dan representasi untuk jenis yang disediakan.
Contoh dalam dokumen ini menggunakan string sebagai representasi objek yang disediakan. Sering kali, mungkin tepat untuk menggunakan objek lain untuk representasi. Misalnya, Anda dapat menggunakan kamus sebagai tas properti:
ProvidedConstructor(parameters = [],
invokeCode= (fun args -> <@@ (new Dictionary<string,obj>()) :> obj @@>))
Sebagai alternatif, Anda dapat menentukan tipe dalam penyedia tipe Anda yang akan digunakan pada waktu proses untuk membentuk representasi, bersama dengan satu atau beberapa operasi pada waktu proses.
type DataObject() =
let data = Dictionary<string,obj>()
member x.RuntimeOperation() = data.Count
Anggota yang disediakan kemudian dapat membuat instans dari jenis objek ini:
ProvidedConstructor(parameters = [],
invokeCode= (fun args -> <@@ (new DataObject()) :> obj @@>))
Dalam hal ini, Anda dapat (opsional) menggunakan jenis ini sebagai penghapusan jenis dengan menentukan jenis ini sebagai baseType saat membuat ProvidedTypeDefinition:
ProvidedTypeDefinition(…, baseType = Some typeof<DataObject> )
…
ProvidedConstructor(…, InvokeCode = (fun args -> <@@ new DataObject() @@>), …)
Pelajaran Utama
Bagian sebelumnya menjelaskan cara membuat penyedia jenis penghapusan sederhana yang menyediakan berbagai jenis, properti, dan metode. Bagian ini juga menjelaskan konsep penghapusan jenis, termasuk beberapa kelebihan dan kekurangan penyediaan jenis yang dihapus dari penyedia jenis, dan membahas representasi jenis yang dihapus.
Penyedia Jenis yang Menggunakan Parameter Statis
Kemampuan untuk membuat parameter penyedia jenis berdasarkan data statis memungkinkan banyak skenario menarik, bahkan dalam kasus ketika penyedia tidak perlu mengakses data lokal atau jarak jauh apa pun. Di bagian ini, Anda akan mempelajari beberapa teknik dasar untuk menyusun penyedia seperti itu.
Ketik Penyedia Regex Yang Diperiksa
Bayangkan Anda ingin menerapkan penyedia tipe untuk ekspresi reguler yang membalut pustaka .NET Regex dalam antarmuka dengan memberikan jaminan waktu kompilasi berikut:
Memverifikasi apakah ekspresi reguler valid.
Menyediakan properti bernama pada kecocokan yang didasarkan pada nama grup apa pun dalam ekspresi reguler.
Bagian ini menunjukkan kepada Anda cara menggunakan penyedia tipe untuk membuat tipe RegexTyped yang diparameterkan oleh pola ekspresi regular untuk memberikan manfaat ini. Pengkompilasi akan melaporkan kesalahan jika pola yang disediakan tidak valid, dan penyedia jenis dapat mengekstrak grup dari pola sehingga Anda dapat mengaksesnya dengan menggunakan properti bernama pada kecocokan. Saat Anda merancang penyedia jenis, Anda harus mempertimbangkan bagaimana API yang diekspos akan terlihat oleh pengguna akhir dan bagaimana desain ini akan diterjemahkan ke kode .NET. Contoh berikut menunjukkan cara menggunakan API seperti itu untuk mendapatkan komponen kode area:
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"
Contoh berikut menunjukkan bagaimana penyedia jenis menerjemahkan panggilan ini:
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"
Perhatikan poin-poin berikut:
Jenis Regex standar mewakili jenis berparameter
RegexTyped.Konstruktor
RegexTypedakan memanggil konstruktor Regex, dengan meneruskan argumen tipe statis untuk pola tersebut.Hasil dari metode
Matchdiwakili oleh jenis standar Match.Setiap grup bernama menghasilkan properti yang disediakan, dan mengakses properti menggunakan pengindeks pada kumpulan kecocokan
Groups.
Kode berikut adalah inti logika untuk menerapkan penyedia seperti itu, dan contoh ini menghilangkan penambahan semua anggota ke jenis yang disediakan. Untuk informasi tentang setiap anggota yang ditambahkan, lihat bagian yang sesuai nanti dalam topik ini.
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 ()
Perhatikan poin-poin berikut:
Penyedia jenis mengambil dua parameter statis:
pattern, yang wajib, danoptions, yang bersifat opsional (karena nilai default disediakan).Setelah argumen statis disediakan, Anda membuat instans ekspresi reguler. Instans ini akan memberikan pengecualian jika Regex salah bentuk, dan kesalahan ini akan dilaporkan kepada pengguna.
DefineStaticParametersDalam panggilan balik, Anda menentukan jenis yang akan dikembalikan setelah argumen disediakan.Kode ini diatur
HideObjectMethodske true sehingga pengalaman IntelliSense akan tetap efisien. Atribut ini menyebabkan anggotaEquals,GetHashCode,Finalize, danGetTypedari objek yang disediakan dihilangkan dari daftar IntelliSense.Anda menggunakan
objsebagai jenis dasar metode, tetapi Anda akan menggunakanRegexobjek sebagai representasi runtime dari jenis ini, seperti yang ditunjukkan contoh berikutnya.Panggilan ke
Regexkonstruktor melempar ArgumentException saat ekspresi reguler tidak valid. Pengkompilasi menangkap pengecualian ini dan melaporkan pesan kesalahan kepada pengguna pada waktu kompilasi atau di editor Visual Studio. Pengecualian ini memungkinkan ekspresi reguler divalidasi tanpa menjalankan aplikasi.
Jenis yang ditentukan di atas belum berguna karena tidak berisi metode atau properti yang bermakna. Pertama, tambahkan metode statis IsMatch :
let isMatch =
ProvidedMethod(
methodName = "IsMatch",
parameters = [ProvidedParameter("input", typeof<string>)],
returnType = typeof<bool>,
isStatic = true,
invokeCode = fun args -> <@@ Regex.IsMatch(%%args[0], pattern) @@>)
isMatch.AddXmlDoc "Indicates whether the regular expression finds a match in the specified input string."
ty.AddMember isMatch
Kode sebelumnya mendefinisikan metode IsMatch, yang mengambil string sebagai input dan mengembalikan bool. Satu-satunya bagian yang sulit adalah penggunaan args argumen dalam InvokeCode definisi. Dalam contoh ini, args adalah daftar kutipan yang mewakili argumen untuk metode ini. Jika metode adalah metode instans, argumen pertama mewakili this argumen . Namun, untuk metode statis, argumen semuanya hanyalah argumen eksplisit untuk metode . Perhatikan bahwa jenis nilai yang dikutip harus cocok dengan jenis pengembalian yang ditentukan (dalam hal ini, bool). Perhatikan juga bahwa kode ini menggunakan AddXmlDoc metode untuk memastikan bahwa metode yang disediakan juga memiliki dokumentasi yang berguna, yang dapat Anda berikan melalui IntelliSense.
Selanjutnya, tambahkan metode Instance Match. Namun, metode ini harus mengembalikan nilai dari tipe Match yang disediakan agar grup dapat diakses dengan cara yang sesuai tipe. Dengan demikian, Anda pertama kali mendeklarasikan jenisnya Match . Karena jenis ini tergantung pada pola yang disediakan sebagai argumen statis, jenis ini harus ditumpuk dalam definisi jenis berparameter:
let matchTy =
ProvidedTypeDefinition(
"MatchType",
baseType = Some baseTy,
hideObjectMethods = true)
ty.AddMember matchTy
Kemudian, Anda menambahkan satu properti pada jenis Kecocokan untuk tiap grup. Pada waktu proses, kecocokan direpresentasikan sebagai Match nilai, sehingga kutipan yang menentukan properti harus menggunakan Groups properti terindeks untuk mendapatkan grup yang relevan.
for group in r.GetGroupNames() do
// Ignore the group named 0, which represents all input.
if group <> "0" then
let prop =
ProvidedProperty(
propertyName = group,
propertyType = typeof<Group>,
getterCode = fun args -> <@@ ((%%args[0]:obj) :?> Match).Groups[group] @@>)
prop.AddXmlDoc($"""Gets the ""{group}"" group from this match""")
matchTy.AddMember prop
Sekali lagi, perhatikan bahwa Anda menambahkan dokumentasi XML ke properti yang disediakan. Perhatikan juga bahwa properti dapat dibaca jika GetterCode fungsi disediakan, dan properti dapat ditulis jika SetterCode fungsi disediakan, sehingga properti yang dihasilkan hanya dibaca.
Sekarang Anda dapat membuat metode instans yang mengembalikan nilai jenis ini 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
Karena Anda membuat metode instans, args[0] mewakili instans RegexTyped tempat metode dipanggil, dan args[1] merupakan argumen input.
Akhirnya, sediakan sebuah konstruktor agar instans dari tipe yang diberikan dapat dibuat.
let ctor =
ProvidedConstructor(
parameters = [],
invokeCode = fun args -> <@@ Regex(pattern, options) :> obj @@>)
ctor.AddXmlDoc("Initializes a regular expression instance.")
ty.AddMember ctor
Konstruktor ini hanya menghapus dalam proses pembuatan instans .NET Regex standar, yang nantinya dikemas ke dalam objek karena obj adalah penghapusan dari tipe yang disediakan. Dengan perubahan tersebut, sampel penggunaan API yang ditentukan sebelumnya dalam topik berfungsi seperti yang diharapkan. Kode berikut selesai dan final:
namespace Samples.FSharp.RegexTypeProvider
open System.Reflection
open Microsoft.FSharp.Core.CompilerServices
open Samples.FSharp.ProvidedTypes
open System.Text.RegularExpressions
[<TypeProvider>]
type public CheckedRegexProvider() as this =
inherit TypeProviderForNamespaces()
// Get the assembly and namespace used to house the provided types.
let thisAssembly = Assembly.GetExecutingAssembly()
let rootNamespace = "Samples.FSharp.RegexTypeProvider"
let baseTy = typeof<obj>
let staticParams = [ProvidedStaticParameter("pattern", typeof<string>)]
let regexTy = ProvidedTypeDefinition(thisAssembly, rootNamespace, "RegexTyped", Some baseTy)
do regexTy.DefineStaticParameters(
parameters=staticParams,
instantiationFunction=(fun typeName parameterValues ->
match parameterValues with
| [| :? string as pattern|] ->
// Create an instance of the regular expression.
let r = System.Text.RegularExpressions.Regex(pattern)
// Declare the typed regex provided type.
let ty =
ProvidedTypeDefinition(
thisAssembly,
rootNamespace,
typeName,
baseType = Some baseTy)
ty.AddXmlDoc "A strongly typed interface to the regular expression '%s'"
// Provide strongly typed version of Regex.IsMatch static method.
let isMatch =
ProvidedMethod(
methodName = "IsMatch",
parameters = [ProvidedParameter("input", typeof<string>)],
returnType = typeof<bool>,
isStatic = true,
invokeCode = fun args -> <@@ Regex.IsMatch(%%args[0], pattern) @@>)
isMatch.AddXmlDoc "Indicates whether the regular expression finds a match in the specified input string"
ty.AddMember isMatch
// Provided type for matches
// Again, erase to obj even though the representation will always be a Match
let matchTy =
ProvidedTypeDefinition(
"MatchType",
baseType = Some baseTy,
hideObjectMethods = true)
// Nest the match type within parameterized Regex type.
ty.AddMember matchTy
// Add group properties to match type
for group in r.GetGroupNames() do
// Ignore the group named 0, which represents all input.
if group <> "0" then
let prop =
ProvidedProperty(
propertyName = group,
propertyType = typeof<Group>,
getterCode = fun args -> <@@ ((%%args[0]:obj) :?> Match).Groups[group] @@>)
prop.AddXmlDoc(sprintf @"Gets the ""%s"" group from this match" group)
matchTy.AddMember(prop)
// Provide strongly typed version of Regex.Match instance method.
let matchMeth =
ProvidedMethod(
methodName = "Match",
parameters = [ProvidedParameter("input", typeof<string>)],
returnType = matchTy,
invokeCode = fun args -> <@@ ((%%args[0]:obj) :?> Regex).Match(%%args[1]) :> obj @@>)
matchMeth.AddXmlDoc "Searches the specified input string for the first occurrence of this regular expression"
ty.AddMember matchMeth
// Declare a constructor.
let ctor =
ProvidedConstructor(
parameters = [],
invokeCode = fun args -> <@@ Regex(pattern) :> obj @@>)
// Add documentation to the constructor.
ctor.AddXmlDoc "Initializes a regular expression instance"
ty.AddMember ctor
ty
| _ -> failwith "unexpected parameter values"))
do this.AddNamespace(rootNamespace, [regexTy])
[<TypeProviderAssembly>]
do ()
Pelajaran Utama
Bagian ini menjelaskan cara membuat penyedia jenis yang beroperasi pada parameter statisnya. Penyedia memeriksa parameter statis dan menyediakan operasi berdasarkan nilainya.
Penyedia jenis yang didukung oleh data lokal
Sering kali Anda mungkin ingin penyedia jenis menyajikan API berdasarkan tidak hanya parameter statis tetapi juga informasi dari sistem lokal atau jarak jauh. Bagian ini membahas penyedia jenis yang didasarkan pada data lokal, seperti file data lokal.
Penyedia File CSV Sederhana
Sebagai contoh sederhana, pertimbangkan penyedia tipe data untuk mengakses data ilmiah dalam format CSV (Comma Separated Value). Bagian ini mengasumsikan bahwa file CSV berisi baris header diikuti dengan data floating point, seperti yang diilustrasikan tabel berikut:
| Jarak (meter) | Waktu (detik) |
|---|---|
| 50.0 | 3.7 |
| 100,0 | 5.2 |
| 150.0 | 6.4 |
Bagian ini memperlihatkan cara menyediakan jenis yang bisa Anda gunakan untuk mendapatkan baris dengan Distance properti jenis float<meter> dan Time properti jenis float<second>. Untuk kesederhanaan, asumsi berikut dibuat:
Nama header tidak memiliki satuan atau berbentuk "Nama (unit)" dan tidak berisi koma.
Unit semuanya adalah unit Sistem Internasional (SI) seperti yang didefinisikan oleh modul FSharp.Data.UnitSystems.SI.UnitNames Module (F#).
Semua unit sederhana (misalnya, meter) daripada senyawa (misalnya, meter/detik).
Semua kolom berisi data titik mengambang.
Penyedia yang lebih lengkap akan melonggarkan pembatasan ini.
Sekali lagi langkah pertama adalah mempertimbangkan bagaimana API akan terlihat.
info.csv Mengingat file dengan konten dari tabel sebelumnya (dalam format yang dipisahkan koma), pengguna penyedia harus dapat menulis kode yang menyerupai contoh berikut:
let info = new MiniCsv<"info.csv">()
for row in info.Data do
let time = row.Time
printfn $"{float time}"
Dalam hal ini, pengkompilasi harus mengonversi panggilan ini menjadi sesuatu seperti contoh berikut:
let info = new CsvFile("info.csv")
for row in info.Data do
let (time:float) = row[1]
printfn $"%f{float time}"
Terjemahan optimal akan mengharuskan penyedia jenis untuk menentukan tipe yang sesungguhnya CsvFile dalam komponen penyedia jenis. Penyedia tipe sering mengandalkan beberapa tipe pembantu dan metode untuk merangkum logika penting. Karena ukuran dihapus pada waktu proses, Anda dapat menggunakan float[] sebagai jenis yang dihapus untuk baris data. Pengkompilasi akan memperlakukan kolom yang berbeda karena memiliki jenis pengukuran yang berbeda. Misalnya, kolom pertama dalam contoh kami memiliki jenis float<meter>, dan yang kedua memiliki float<second>. Namun, representasi yang dihapus dapat tetap cukup sederhana.
Kode berikut menunjukkan inti implementasi.
// Simple type wrapping CSV data
type CsvFile(filename) =
// Cache the sequence of all data lines (all lines but the first)
let data =
seq {
for line in File.ReadAllLines(filename) |> Seq.skip 1 ->
line.Split(',') |> Array.map float
}
|> Seq.cache
member _.Data = data
[<TypeProvider>]
type public MiniCsvProvider(cfg:TypeProviderConfig) as this =
inherit TypeProviderForNamespaces(cfg)
// Get the assembly and namespace used to house the provided types.
let asm = System.Reflection.Assembly.GetExecutingAssembly()
let ns = "Samples.FSharp.MiniCsvProvider"
// Create the main provided type.
let csvTy = ProvidedTypeDefinition(asm, ns, "MiniCsv", Some(typeof<obj>))
// Parameterize the type by the file to use as a template.
let filename = ProvidedStaticParameter("filename", typeof<string>)
do csvTy.DefineStaticParameters([filename], fun tyName [| :? string as filename |] ->
// Resolve the filename relative to the resolution folder.
let resolvedFilename = Path.Combine(cfg.ResolutionFolder, filename)
// Get the first line from the file.
let headerLine = File.ReadLines(resolvedFilename) |> Seq.head
// Define a provided type for each row, erasing to a float[].
let rowTy = ProvidedTypeDefinition("Row", Some(typeof<float[]>))
// Extract header names from the file, splitting on commas.
// use Regex matching to get the position in the row at which the field occurs
let headers = Regex.Matches(headerLine, "[^,]+")
// Add one property per CSV field.
for i in 0 .. headers.Count - 1 do
let headerText = headers[i].Value
// Try to decompose this header into a name and unit.
let fieldName, fieldTy =
let m = Regex.Match(headerText, @"(?<field>.+) \((?<unit>.+)\)")
if m.Success then
let unitName = m.Groups["unit"].Value
let units = ProvidedMeasureBuilder.Default.SI unitName
m.Groups["field"].Value, ProvidedMeasureBuilder.Default.AnnotateType(typeof<float>,[units])
else
// no units, just treat it as a normal float
headerText, typeof<float>
let prop =
ProvidedProperty(fieldName, fieldTy,
getterCode = fun [row] -> <@@ (%%row:float[])[i] @@>)
// Add metadata that defines the property's location in the referenced file.
prop.AddDefinitionLocation(1, headers[i].Index + 1, filename)
rowTy.AddMember(prop)
// Define the provided type, erasing to CsvFile.
let ty = ProvidedTypeDefinition(asm, ns, tyName, Some(typeof<CsvFile>))
// Add a parameterless constructor that loads the file that was used to define the schema.
let ctor0 =
ProvidedConstructor([],
invokeCode = fun [] -> <@@ CsvFile(resolvedFilename) @@>)
ty.AddMember ctor0
// Add a constructor that takes the file name to load.
let ctor1 = ProvidedConstructor([ProvidedParameter("filename", typeof<string>)],
invokeCode = fun [filename] -> <@@ CsvFile(%%filename) @@>)
ty.AddMember ctor1
// Add a more strongly typed Data property, which uses the existing property at run time.
let prop =
ProvidedProperty("Data", typedefof<seq<_>>.MakeGenericType(rowTy),
getterCode = fun [csvFile] -> <@@ (%%csvFile:CsvFile).Data @@>)
ty.AddMember prop
// Add the row type as a nested type.
ty.AddMember rowTy
ty)
// Add the type to the namespace.
do this.AddNamespace(ns, [csvTy])
Perhatikan poin-poin berikut tentang implementasi:
Konstruktor yang kelebihan beban memungkinkan file asli atau yang memiliki skema identik untuk dibaca. Pola ini umum ketika Anda menulis penyedia jenis untuk sumber data lokal atau jarak jauh, dan pola ini memungkinkan file lokal digunakan sebagai templat untuk data jarak jauh.
Anda dapat menggunakan nilai TypeProviderConfig yang diteruskan ke konstruktor penyedia jenis untuk mengatasi nama file relatif.
Anda dapat menggunakan
AddDefinitionLocationmetode untuk menentukan lokasi properti yang disediakan. Oleh karena itu, jika Anda menggunakanGo To Definitionpada properti yang disediakan, file CSV akan terbuka di Visual Studio.Anda dapat menggunakan
ProvidedMeasureBuildertipe untuk mencari satuan SI dan menghasilkan tipefloat<_>yang relevan.
Pelajaran Utama
Bagian ini menjelaskan cara membuat penyedia jenis untuk sumber data lokal dengan skema sederhana yang terkandung dalam sumber data itu sendiri.
Melaju Lebih Jauh
Bagian berikut mencakup saran untuk studi lebih lanjut.
Lihat Kode yang Dikompilasi untuk Jenis yang Dihapus
Untuk memberi Anda beberapa gambaran tentang bagaimana penggunaan penyedia tipe sesuai dengan kode yang dihasilkan, lihat fungsi berikut dengan menggunakan HelloWorldTypeProvider yang digunakan sebelumnya dalam topik ini.
let function1 () =
let obj1 = Samples.HelloWorldTypeProvider.Type1("some data")
obj1.InstanceProperty
Berikut adalah gambar kode yang dihasilkan yang didekompilasi dengan menggunakan 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
Seperti yang ditunjukkan contoh, semua penyebutan jenis Type1 dan InstanceProperty properti telah dihapus, hanya menyisakan operasi pada jenis runtime yang terlibat.
Konvensi Desain dan Penamaan untuk Penyuplai Tipe
Perhatikan konvensi berikut saat membuat penyedia tipe.
Penyedia untuk Protokol Konektivitas Secara umum, nama sebagian besar DLL penyedia untuk protokol konektivitas data dan layanan, seperti koneksi OData atau SQL, harus berakhir di TypeProvider atau TypeProviders. Misalnya, gunakan nama DLL yang menyerupai string berikut:
Fabrikam.Management.BasicTypeProviders.dll
Pastikan bahwa jenis yang Anda berikan adalah anggota namespace yang sesuai, dan tunjukkan protokol konektivitas yang Anda terapkan:
Fabrikam.Management.BasicTypeProviders.WmiConnection<…>
Fabrikam.Management.BasicTypeProviders.DataProtocolConnection<…>
Penyedia Utilitas untuk Pengkodian Umum. Untuk penyedia jenis utilitas seperti itu untuk ekspresi reguler, penyedia jenis mungkin menjadi bagian dari pustaka dasar, seperti yang ditunjukkan contoh berikut:
#r "Fabrikam.Core.Text.Utilities.dll"
Dalam hal ini, jenis yang disediakan akan muncul pada titik yang sesuai sesuai dengan konvensi desain .NET normal:
open Fabrikam.Core.Text.RegexTyped
let regex = new RegexTyped<"a+b+a+b+">()
Sumber Data Singleton. Beberapa penyedia jenis terhubung ke satu sumber data khusus dan hanya menyediakan data. Dalam hal ini, Anda harus menghilangkan akhiran TypeProvider dan menggunakan konvensi normal untuk penamaan .NET:
#r "Fabrikam.Data.Freebase.dll"
let data = Fabrikam.Data.Freebase.Astronomy.Asteroids
Untuk informasi selengkapnya, lihat GetConnection konvensi desain yang dijelaskan nanti dalam topik ini.
Pola Desain untuk Penyedia Tipe
Bagian berikut menjelaskan pola desain yang dapat Anda gunakan saat membuat penyedia tipe.
Pola Desain GetConnection
Sebagian besar penyedia tipe harus ditulis untuk menggunakan pola GetConnection yang digunakan oleh penyedia tipe di FSharp.Data.TypeProviders.dll, seperti yang ditunjukkan pada contoh berikut ini.
#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
Penyedia Tipe yang Didukung oleh Data dan Layanan Jarak Jauh
Sebelum Anda membuat penyedia jenis yang didukung oleh data dan layanan jarak jauh, Anda harus mempertimbangkan berbagai masalah yang melekat dalam pemrograman yang terhubung. Masalah ini termasuk pertimbangan berikut:
pemetaan skema
Keberlangsungan dan peniadaan dalam konteks perubahan skema
penembolokan skema
implementasi asinkron dari operasi akses data
kueri pendukung, termasuk kueri LINQ
kredensial dan autentikasi
Topik ini tidak menjelajahi masalah ini lebih lanjut.
Teknik Penulisan Tambahan
Saat Anda menulis penyedia tipe Anda sendiri, Anda mungkin ingin menggunakan teknik tambahan berikut.
Membuat Jenis dan Anggota Sesuai Permintaan
Api ProvidedType memiliki versi AddMember yang tertunda.
type ProvidedType =
member AddMemberDelayed : (unit -> MemberInfo) -> unit
member AddMembersDelayed : (unit -> MemberInfo list) -> unit
Versi ini digunakan untuk membuat ruang dari berbagai jenis sesuai permintaan.
Menyediakan Tipe Array dan Instansiasi Tipe Generik
Anda membuat anggota yang disediakan (yang tanda tangannya mencakup jenis array, jenis byref, dan instans jenis generik) dengan menggunakan MakeArrayType, MakePointerType, dan MakeGenericType secara normal pada instans apa pun dari Type, termasuk ProvidedTypeDefinitions.
Nota
Dalam beberapa kasus, Anda mungkin harus menggunakan alat bantu di ProvidedTypeBuilder.MakeGenericType. Lihat dokumentasi SDK Penyedia Jenis untuk detail selengkapnya.
Menyediakan Anotasi Satuan Ukuran
API ProvidedTypes menyediakan pembantu untuk memberikan anotasi pengukuran. Misalnya, untuk memberikan jenis float<kg>, gunakan kode berikut:
let measures = ProvidedMeasureBuilder.Default
let kg = measures.SI "kilogram"
let m = measures.SI "meter"
let float_kg = measures.AnnotateType(typeof<float>,[kg])
Untuk menyediakan jenis Nullable<decimal<kg/m^2>>, gunakan kode berikut:
let kgpm2 = measures.Ratio(kg, measures.Square m)
let dkgpm2 = measures.AnnotateType(typeof<decimal>,[kgpm2])
let nullableDecimal_kgpm2 = typedefof<System.Nullable<_>>.MakeGenericType [|dkgpm2 |]
Mengakses Sumber Daya Project-Local atau Script-Local
Setiap instans dari penyedia tipe dapat diberikan TypeProviderConfig nilai selama pembuatan. Nilai ini berisi "folder resolusi" untuk penyedia (yaitu, folder proyek untuk kompilasi atau direktori yang berisi skrip), daftar rakitan yang direferensikan, dan informasi lainnya.
Peniadaan
Penyedia dapat menaikkan sinyal pembatalan untuk memberi tahu layanan bahasa F# bahwa asumsi skema mungkin telah berubah. Ketika invalidasi terjadi, pemeriksaan tipe diulang jika penyedia dihosting di Visual Studio. Sinyal ini akan diabaikan ketika penyedia layanan dihosting di F# Interactive atau oleh F# Compiler (fsc.exe).
Informasi Skema Cache
Penyedia harus sering menyimpan akses cache ke informasi skema. Data yang di-cache harus disimpan dengan menggunakan nama file yang diberikan sebagai parameter statis atau sebagai data pengguna. Contoh caching skema adalah parameter LocalSchemaFile dalam penyedia tipe di assembly FSharp.Data.TypeProviders. Dalam implementasi penyedia ini, parameter statis ini mengarahkan penyedia jenis untuk menggunakan informasi skema dalam file lokal yang ditentukan alih-alih mengakses sumber data melalui jaringan. Untuk menggunakan informasi skema yang di-cache, Anda juga harus mengatur parameter ForceUpdate statis ke false. Anda dapat menggunakan teknik serupa untuk mengaktifkan akses data online dan offline.
Rakitan Pembackupan
Saat Anda mengompilasi file .dll atau .exe, file .dll pendukung untuk jenis yang dihasilkan secara statis ditautkan ke dalam rakitan yang dihasilkan. Tautan ini dibuat dengan menyalin definisi jenis Bahasa Perantara (IL) dan sumber daya terkelola apa pun dari rakitan pencadangan ke dalam rakitan akhir. Saat Anda menggunakan F# Interactive, file .dll cadangan tidak disalin dan sebaliknya dimuat langsung ke dalam proses F# Interactive.
Pengecualian dan Diagnostik dari Penyedia Tipe
Penggunaan semua anggota dari jenis yang disediakan kemungkinan dapat melemparkan pengecualian. Dalam semua kasus, jika penyedia jenis melemparkan pengecualian, pengkompilasi host mengaitkan kesalahan ke penyedia jenis tertentu.
Pengecualian penyedia jenis tidak boleh mengakibatkan kesalahan kompilator internal.
Penyedia tipe tidak dapat melaporkan peringatan.
Ketika penyedia jenis dihosting di pengkompilasi F#, lingkungan pengembangan F#, atau F# Interaktif, semua pengecualian dari penyedia tersebut tertangkap. Properti Pesan selalu berisi teks kesalahan, dan tidak ada stack trace yang muncul. Jika Anda akan melemparkan pengecualian, Anda dapat melemparkan contoh berikut:
System.NotSupportedException, ,System.IO.IOExceptionSystem.Exception.
Menyediakan Jenis yang Dihasilkan
Sejauh ini, dokumen ini telah menjelaskan cara menyediakan tipe yang terhapus. Anda juga dapat menggunakan mekanisme penyedia jenis di F# untuk menyediakan jenis yang dihasilkan, yang ditambahkan sebagai definisi jenis .NET nyata ke dalam program pengguna. Anda harus merujuk ke jenis bawaan yang dihasilkan dengan menggunakan definisi jenis.
open Microsoft.FSharp.TypeProviders
type Service = ODataService<"http://services.odata.org/Northwind/Northwind.svc/">
Kode pembantu ProvidedTypes-0.2 yang merupakan bagian dari rilis F# 3.0 hanya memiliki dukungan terbatas untuk menyediakan jenis yang dihasilkan. Pernyataan berikut harus benar untuk definisi jenis yang dihasilkan:
isErasedharus diatur kefalse.Jenis yang dihasilkan harus ditambahkan ke
ProvidedAssembly()yang baru saja dibangun, yang berfungsi sebagai wadah untuk fragmen kode yang dihasilkan.Penyedia harus memiliki assembly yang memiliki dukungan sebenarnya dari file .NET .dll dengan file .dll yang sesuai pada disk.
Aturan dan Batasan
Saat Anda mengembangkan penyedia tipe, ingatlah aturan dan batasan berikut.
Jenis yang disediakan harus dapat dijangkau
Semua jenis yang disediakan harus dapat dijangkau dari jenis yang tidak bersarang. Jenis yang tidak berlapis diberikan dalam pemanggilan ke konstruktor TypeProviderForNamespaces atau AddNamespace. Misalnya, jika penyedia menyediakan jenis StaticClass.P : T, Anda harus memastikan bahwa T adalah jenis yang tidak berlapis atau berlapis di bawah satu.
Misalnya, beberapa penyedia memiliki kelas statis seperti DataTypes yang berisi jenis ini T1, T2, T3, ... . Jika tidak, kesalahan mengatakan bahwa referensi ke jenis T dalam perakitan A ditemukan, tetapi jenisnya tidak dapat ditemukan dalam perakitan tersebut. Jika kesalahan ini muncul, verifikasi bahwa semua subjenis Anda dapat dijangkau dari jenis penyedia. Catatan: Jenis-jenis ini T1, T2, T3... disebut sebagai jenis on-the-fly. Ingatlah untuk menempatkannya di namespace yang dapat diakses atau jenis induk.
Batasan Mekanisme Penyedia Tipe
Mekanisme penyedia jenis di F# memiliki batasan berikut:
Infrastruktur yang mendasar untuk penyedia jenis di F# tidak mendukung jenis generik yang disediakan atau metode generik yang disediakan.
Mekanisme tidak mendukung jenis berlapis dengan parameter statis.
Tips Pengembangan
Anda mungkin menemukan tips berikut yang berguna selama proses pengembangan:
Menjalankan dua instans Visual Studio
Anda dapat mengembangkan penyedia jenis dalam satu instans dan menguji penyedia di instans lain karena IDE pengujian akan mengunci file .dll yang mencegah penyedia jenis dibangun kembali. Dengan demikian, Anda harus menutup instans kedua Visual Studio saat penyedia dibangun dalam instans pertama, lalu Anda harus membuka kembali instans kedua setelah penyedia dibuat.
Mendebug penyedia tipe dengan memanggil fsc.exe
Anda dapat memanggil penyedia jenis dengan menggunakan alat berikut:
fsc.exe (pengkompilasi baris perintah F#)
fsi.exe (Pengkompilasi Interaktif F# )
devenv.exe (Visual Studio)
Anda sering dapat men-debug penyedia jenis paling mudah dengan menggunakan fsc.exe pada file skrip pengujian (misalnya, script.fsx). Anda dapat meluncurkan debugger dari jendela perintah.
devenv /debugexe fsc.exe script.fsx
Anda dapat menggunakan pengelogan print-to-stdout.