Tutorial: Membuat Penyedia Jenis

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 Jenis.

Ekosistem F# berisi berbagai jenis penyedia untuk layanan data Internet dan enterprise yang umum digunakan. Misalnya:

  • 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 jenis untuk penyematan T-SQL waktu 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 himpunan data bernama dalam jumlah besar dan terus bertambah, masing-masing dengan skema data stabilnya sendiri. Anda dapat membuat penyedia jenis yang membaca skema dan menyajikan himpunan data saat ini kepada pemrogram dengan cara yang berjenis kuat.

Sebelum Memulai

Mekanisme penyedia jenis terutama dirancang untuk memasukkan 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 pemrograman meta bahasa intra, meskipun domain tersebut berisi beberapa kegunaan yang valid. Anda harus menggunakan mekanisme ini hanya jika diperlukan dan di mana pengembangan penyedia jenis menghasilkan nilai yang sangat tinggi.

Anda harus menghindari menulis penyedia jenis saat skema tidak tersedia. Demikian juga, Anda harus menghindari penulisan penyedia jenis di mana pustaka .NET biasa (atau bahkan yang sudah ada) sudah cukup.

Sebelum memulai, Anda mungkin mengajukan pertanyaan berikut:

  • Apakah Anda memiliki skema untuk sumber informasi Anda? Jika demikian, apa pemetaan ke dalam sistem jenis F# dan .NET?

  • Bisakah Anda menggunakan API yang ada (diketik secara dinamis) sebagai titik awal untuk implementasi Anda?

  • Apakah Anda dan organisasi Anda memiliki cukup penggunaan penyedia jenis untuk membuat tulisan itu bermanfaat? Apakah pustaka .NET normal memenuhi kebutuhan Anda?

  • Berapa banyak skema Anda akan berubah?

  • Apakah itu akan berubah selama pengodean?

  • Apakah itu akan berubah di antara sesi pengodean?

  • Apakah akan berubah selama eksekusi program?

Penyedia jenis paling cocok untuk situasi di mana skema stabil pada waktu berjalan dan selama masa kode yang dikompilasi.

Type Provider Sederhana

Contoh ini adalah Samples.HelloWorldTypeProvider, mirip dengan contoh di direktori examples dari 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 jenis yang dihapus, lihat Detail Tentang Jenis yang Dihapus yang Disediakan nanti di 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 set 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 Visual Studio yang terpisah, buat skrip F#, lalu tambahkan referensi ke penyedia dari skrip Anda dengan menggunakan #r seperti yang ditunjukkan oleh 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 jenis di bawah namespace layanan Samples.HelloWorldTypeProvider yang dihasilkan oleh penyedia jenis.

Sebelum Anda mengompilasi ulang penyedia, pastikan bahwa Anda telah menutup semua contoh Visual Studio dan F# Interactive yang menggunakan penyedia DLL. Jika tidak, kesalahan build akan terjadi karena output DLL akan dikunci.

Untuk men-debug penyedia ini dengan menggunakan pernyataan cetak, buat skrip yang memperlihatkan 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 Prompt Perintah Pengembang untuk Visual Studio dengan mandat administratif, dan jalankan perintah berikut ini:

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 devenv lain 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 lengkap 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 Kode dengan Debugger. Selain itu, Anda juga dapat mengatur penangkapan pengecualian kesempatan pertama dengan membuka menu Debug lalu 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 centang Thrown.

Implementasi Penyedia Jenis

Bagian ini memandu Anda melalui bagian utama dari implementasi penyedia jenis. Pertama, Anda menentukan jenis untuk penyedia jenis kustom itu sendiri:

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

Jenis ini harus bersifat publik, dan Anda harus menandainya dengan atribut TypeProvider sehingga pengompilasi akan mengenali penyedia jenis saat proyek F# terpisah mereferensikan rakitan yang berisi jenis tersebut. Parameter konfigurasi bersifat opsional, dan, jika ada, berisi informasi konfigurasi kontekstual untuk instans penyedia jenis yang dibuat oleh pengompilasi F#.

Selanjutnya, Anda mengimplementasikan antarmuka ITypeProvider. Dalam hal ini, Anda menggunakan jenis TypeProviderForNamespaces dari ProvidedTypes API sebagai jenis dasar. Jenis pembantu ini dapat menyediakan kumpulan namespace layanan yang disediakan eagerly, yang masing-masing secara langsung berisi jumlah terbatas dari jenis yang tetap dan disediakan eagerly. Dalam konteks ini, penyedia bersemangat menghasilkan jenis meskipun tidak diperlukan atau digunakan.

inherit TypeProviderForNamespaces(config)

Selanjutnya, tentukan nilai privat lokal yang menentukan namespace layanan untuk jenis yang disediakan, dan temukan rakitan penyedia jenis itu sendiri. Rakitan ini digunakan kemudian sebagai jenis induk logis dari jenis terhapus yang disediakan.

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

Selanjutnya, buat fungsi untuk menyediakan masing-masing jenis Type1…Type100. Fungsi ini dijelaskan secara lebih rinci nanti dalam topik ini.

let makeOneProvidedType (n:int) = …

Selanjutnya, buat 100 jenis yang disediakan:

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

Selanjutnya, tambahkan jenis sebagai namespace layanan yang disediakan:

do this.AddNamespace(namespaceName, types)

Terakhir, tambahkan atribut rakitan yang menunjukkan bahwa Anda membuat DLL penyedia jenis:

[<assembly:TypeProviderAssembly>]
do()

Menyediakan Satu Jenis Dan Anggotanya

Fungsi makeOneProvidedType melakukan pekerjaan nyata dalam menyediakan salah satu jenis.

let makeOneProvidedType (n:int) =
…

Langkah ini menjelaskan implementasi fungsi ini. Pertama, buat jenis yang disediakan (misalnya, Type1, when n = 1, atau Type57, when 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 memperhatikan 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 jenis non-bersarang, Anda harus menentukan rakitan dan namespace layanan. Untuk jenis yang terhapus, rakitan harus menjadi rakitan penyedia jenis itu sendiri.

Selanjutnya, tambahkan dokumentasi XML ke jenis. Dokumentasi ini tertunda, yaitu, dihitung sesuai permintaan jika pengompilasi 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 mengevaluasi string "Halo!". GetterCode untuk properti menggunakan kutipan F#, yang mewakili kode yang dihasilkan oleh pengompilasi host untuk mendapatkan properti. Untuk informasi selengkapnya tentang kutipan, lihat Kode Kutipan (F#).

Tambahkan dokumentasi XML ke properti.

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

Sekarang lampirkan properti yang disediakan ke jenis yang disediakan. Anda harus melampirkan anggota yang disediakan ke satu dan hanya satu jenis. Jika tidak, anggota tidak akan pernah dapat diakses.

t.AddMember staticProp

Sekarang buat konstruktor yang disediakan yang tidak menggunakan parameter.

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

InvokeCode untuk konstruktor mengembalikan kutipan F#, yang mewakili kode yang dihasilkan oleh pengompilasi host saat konstruktor dipanggil. Misalnya, Anda dapat menggunakan konstruktor berikut:

new Type10()

Instans dari jenis yang disediakan akan dibuat dengan data dasar "Data objek". Kode yang dikutip mencakup konversi ke obj karena jenis itu adalah penghapusan dari jenis yang disediakan ini (seperti yang Anda tentukan saat 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 yang disediakan kedua yang mengambil satu parameter:

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

InvokeCode untuk konstruktor kembali mengembalikan kutipan F#, yang mewakili kode yang dihasilkan oleh pengompilasi host untuk panggilan ke metode. Misalnya, Anda dapat menggunakan konstruktor berikut:

new Type10("ten")

Instans dari jenis yang disediakan dibuat dengan data dasar "sepuluh". Anda mungkin telah memperhatikan bahwa fungsi InvokeCode mengembalikan 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 kembalian ke jenis terhapus obj. Setelah Anda menambahkan konstruktor yang disediakan kedua ke jenis tersebut, Anda membuat properti instans 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 oleh pengompilasi host untuk mendapatkan properti. Seperti InvokeCode, fungsi GetterCode mengembalikan kutipan. Pengompilasi host memanggil fungsi ini dengan daftar argumen. Dalam hal ini, argumen hanya menyertakan satu ekspresi yang mewakili instans di mana pengambil dipanggil, yang dapat Anda akses dengan menggunakan args[0]. Implementasi GetterCode kemudian disambung ke dalam kutipan hasil pada jenis terhapus obj, dan pemeran digunakan untuk memenuhi mekanisme pengompilasi untuk memeriksa jenis bahwa objek 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 bersarang yang berisi 100 properti bersarang. Pembuatan jenis bersarang 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 Dihapus

Contoh di bagian ini hanya menyediakan jenis yang disediakan terhapus , yang sangat berguna dalam situasi berikut:

  • Saat Anda menulis penyedia untuk ruang informasi yang hanya berisi data dan metode.

  • Saat Anda menulis penyedia di mana semantik jenis runtime bahasa umum yang akurat tidak penting untuk penggunaan praktis ruang informasi.

  • Saat Anda menulis penyedia untuk ruang informasi yang begitu besar dan saling berhubungan sehingga secara teknis tidak layak untuk menghasilkan jenis .NET nyata untuk ruang informasi.

Dalam contoh ini, setiap jenis yang disediakan dihapus menjadi jenis obj, dan semua penggunaan jenis tersebut akan muncul sebagai jenis obj dalam kode yang dikompilasi. Sebenarnya, objek yang mendasari dalam contoh ini adalah string, tetapi jenisnya akan muncul sebagai System.Object dalam kode yang dikompilasi .NET. Seperti halnya semua penggunaan penghapusan jenis, Anda dapat menggunakan boxing, unboxing, dan casting eksplisit untuk menumbangkan jenis yang dihapus. Dalam hal ini, pengecualian pemeran yang tidak valid dapat terjadi saat objek digunakan. Runtime bahasa umum penyedia dapat menentukan jenis representasi pribadinya sendiri untuk membantu melindungi dari representasi yang salah. Anda tidak dapat menentukan jenis yang terhapus di F# itu sendiri. Hanya jenis yang disediakan yang dapat dihapus. Anda harus memahami konsekuensi, baik praktis maupun semantik, menggunakan jenis yang dihapus untuk penyedia jenis Anda atau penyedia yang menyediakan jenis yang dihapus. Jenis terhapus tidak memiliki jenis .NET asli. Oleh karena itu, Anda tidak dapat melakukan refleksi yang akurat atas jenis tersebut, dan Anda mungkin menumbangkan jenis yang terhapus jika Anda menggunakan runtime bahasa umum cast dan teknik lain yang mengandalkan semantik jenis runtime bahasa umum yang tepat. Subversi dari jenis yang terhapus sering menghasilkan pengecualian jenis cast pada durasi.

Memilih Representasi untuk Jenis yang Disediakan Dihapus

Untuk beberapa penggunaan jenis yang disediakan terhapus, tidak diperlukan representasi. Misalnya, jenis yang disediakan yang dihapus mungkin hanya berisi properti statis dan anggota dan tidak ada konstruktor, dan tidak ada metode atau properti yang akan mengembalikan instans jenis. Jika Anda dapat mencapai contoh dari jenis yang disediakan terhapus, Anda harus mempertimbangkan pertanyaan berikut:

Apa yang dimaksud dengan penghapusan dari jenis yang disediakan?

  • 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 terhapus yang disediakan selalu System.Object.

Apa representasi dari jenis yang disediakan?

  • Kumpulan objek yang mungkin untuk jenis yang disediakan yang dihapus disebut representasinya. Dalam contoh dalam dokumen ini, representasi dari semua jenis yang disediakan terhapus Type1..Type100 selalu berupa objek string.

Semua representasi dari jenis yang disediakan harus kompatibel dengan penghapusan jenis yang disediakan. (Jika tidak, pengompilasi 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 masih masuk akal saat menggunakan versi berjenis kuat.

  • Jika Anda ingin membuat API yang berbeda secara signifikan dari .NET API yang ada, masuk akal untuk membuat jenis runtime bahasa umum yang akan menjadi jenis penghapusan dan representasi untuk jenis yang disediakan.

Contoh dalam dokumen ini menggunakan string sebagai representasi dari objek yang disediakan. Seringkali, 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 jenis di penyedia jenis Anda yang akan digunakan pada durasi untuk membentuk representasi, bersama dengan satu atau beberapa operasi runtime bahasa umum:

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 dalam menyediakan 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 semacam itu.

Ketik Penyedia Regex yang Dicentang

Bayangkan Anda ingin menerapkan penyedia jenis untuk ekspresi reguler yang membungkus pustaka .NET Regex dalam antarmuka yang 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 memperlihatkan kepada Anda cara menggunakan penyedia jenis untuk membuat jenis RegexTyped yang diparameterkan pola ekspresi reguler untuk memberikan manfaat ini. Pengompilasi akan melaporkan kesalahan jika pola yang diberikan 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 dieksposnya harus terlihat oleh pengguna akhir dan bagaimana desain ini akan diterjemahkan ke kode .NET. Contoh berikut menunjukkan cara menggunakan API semacam 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 berikut:

  • Jenis Regex standar mewakili jenis RegexTyped berparameter.

  • Konstruktor RegexTyped menghasilkan panggilan ke konstruktor Regex, meneruskan argumen jenis statis untuk pola tersebut.

  • Hasil metode Match diwakili oleh jenis Match standar.

  • Setiap grup bernama menghasilkan properti yang disediakan, dan mengakses properti menghasilkan penggunaan pengindeks pada koleksi Groups kecocokan.

Kode berikut adalah inti dari logika untuk mengimplementasikan penyedia semacam 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 berikut:

  • Penyedia jenis mengambil dua parameter statis: pattern, yang wajib, dan options, yang bersifat opsional (karena nilai default disediakan).

  • Setelah argumen statis diberikan, Anda membuat turunan dari ekspresi reguler. Instans ini akan memberikan pengecualian jika Regex salah bentuk, dan kesalahan ini akan dilaporkan kepada pengguna.

  • Dalam panggilan balik DefineStaticParameters, Anda menentukan jenis yang akan dikembalikan setelah argumen diberikan.

  • Kode ini diatur HideObjectMethods ke true sehingga pengalaman IntelliSense akan tetap efisien. Atribut ini menyebabkan anggota Equals, GetHashCode, Finalize, dan GetType disembunyikan dari daftar IntelliSense untuk objek yang disediakan.

  • Anda menggunakan obj sebagai jenis dasar metode, tetapi Anda akan menggunakan objek Regex sebagai representasi runtime bahasa umum dari jenis ini, seperti yang ditunjukkan contoh berikutnya.

  • Panggilan ke Regex konstruktor melempar ArgumentException saat ekspresi reguler tidak valid. Pengompilasi 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 mengandung metode atau properti yang berarti. Pertama, tambahkan metode IsMatch statis :

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 argumen args dalam definisi InvokeCode. Dalam contoh ini, args adalah daftar kutipan yang mewakili argumen untuk metode ini. Jika metodenya adalah metode instans, argumen pertama mewakili argumen this. Namun, untuk metode statis, semua argumen hanyalah argumen eksplisit untuk metode tersebut. Perhatikan bahwa jenis nilai yang dikutip harus cocok dengan jenis pengembalian yang ditentukan (dalam hal ini, bool ). Perhatikan juga bahwa kode ini menggunakan metode AddXmlDoc untuk memastikan bahwa metode yang disediakan juga memiliki dokumentasi yang berguna, yang dapat Anda berikan melalui IntelliSense.

Selanjutnya, tambahkan metode Pencocokan instans. Namun, metode ini harus mengembalikan nilai dari jenis Match yang disediakan sehingga grup dapat diakses dengan cara yang diketik dengan kuat. Dengan demikian, Anda terlebih dahulu mendeklarasikan jenisnya Match. Karena jenis ini tergantung pada pola yang disediakan sebagai argumen statis, jenis ini harus ditumpuk dalam definisi jenis parameter:

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

ty.AddMember matchTy

Anda kemudian menambahkan satu properti ke jenis Pencocokan untuk setiap grup. Saat dijalankan, kecocokan direpresentasikan sebagai nilai Match, jadi kutipan yang mendefinisikan properti harus menggunakan properti terindeks Groups 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 fungsi GetterCode disediakan, dan properti dapat ditulis jika fungsi SetterCode disediakan, sehingga properti yang dihasilkan hanya dapat dibaca.

Sekarang Anda dapat membuat metode instans yang mengembalikan nilai jenis Match ini :

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 RegexTyped instans tempat metode dipanggil, dan args[1] adalah argumen masukan.

Terakhir, sediakan konstruktor sehingga instans dari jenis yang disediakan dapat dibuat.

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

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

ty.AddMember ctor

Konstruktor hanya menghapus pembuatan instans .NET Regex standar, yang kembali dikotak ke objek karena obj merupakan penghapusan jenis yang disediakan. Dengan perubahan itu, contoh 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

Seringkali Anda mungkin ingin penyedia jenis menyajikan API tidak hanya berdasarkan 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 jenis untuk mengakses data ilmiah dalam format Nilai Terpisah Koma (CSV). Bagian ini mengasumsikan bahwa file CSV berisi baris header yang diikuti oleh data titik mengambang, seperti yang diilustrasikan tabel berikut:

Jarak (meter) Waktu (detik)
50.0 3.7
100,0 5.2
150.0 6.4

Bagian ini menunjukkan cara menyediakan jenis yang dapat Anda gunakan untuk mendapatkan baris dengan properti Distance berjenis float<meter> dan Time properti berjenis float<second>. Untuk mempermudah, asumsi berikut dibuat:

  • Nama header tidak memiliki unit atau memiliki bentuk "Nama (unit)" dan tidak mengandung koma.

  • Unit adalah semua unit System International (SI) sebagai 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 tampilan API. Mengingat file dengan konten dari tabel sebelumnya (dalam format yang info.csv 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 yang optimal akan membutuhkan penyedia jenis untuk mendefinisikan jenis CsvFile yang sebenarnya dalam rakitan penyedia jenis. Penyedia jenis sering mengandalkan beberapa jenis dan metode pembantu untuk membungkus logika penting. Karena ukuran dihapus pada saat dijalankan, Anda dapat menggunakan float[] sebagai jenis yang dihapus untuk satu baris. Pengompilasi akan memperlakukan kolom yang berbeda sebagai memiliki jenis pengukuran yang berbeda. Misalnya, kolom pertama dalam contoh kita memiliki jenis float<meter>, dan kolom kedua memiliki float<second>. Namun, representasi yang terhapus bisa tetap sangat sederhana.

Kode berikut menunjukkan inti dari 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 implementasinya:

  • Konstruktor yang kelebihan beban memungkinkan file asli atau yang memiliki skema identik untuk dibaca. Pola ini umum terjadi saat Anda menulis penyedia jenis untuk sumber data lokal atau jauh, dan pola ini memungkinkan file lokal digunakan sebagai template untuk data jarak jauh.

  • Anda dapat menggunakan nilai TypeProviderConfig yang diteruskan ke konstruktor penyedia jenis untuk menyelesaikan nama file relatif.

  • Anda dapat menggunakan metode AddDefinitionLocation untuk menentukan lokasi properti yang disediakan. Oleh karena itu, jika Anda menggunakan Go To Definition pada properti yang disediakan, file CSV akan terbuka di Visual Studio.

  • Anda dapat menggunakan jenis ProvidedMeasureBuilder untuk mencari satuan SI dan menghasilkan jenis float<_> yang relevan.

Pelajaran Utama

Bagian ini menjelaskan cara membuat penyedia jenis untuk sumber data lokal dengan skema sederhana yang terdapat dalam sumber data itu sendiri.

Melangkah Lebih Jauh

Bagian berikut mencakup saran untuk studi lebih lanjut.

Lihatlah Kode yang Dikompilasi untuk Jenis yang Dihapus

Untuk memberi Anda gambaran tentang bagaimana penggunaan penyedia jenis sesuai dengan kode yang dikeluarkan, 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 hasil dekompilasi 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 oleh contoh, semua penyebutan jenis Type1 dan properti InstanceProperty telah dihapus, hanya menyisakan operasi pada jenis runtime bahasa umum yang terlibat.

Konvensi Desain dan Penamaan untuk Penyedia Jenis

Amati konvensi berikut saat penulisan penyedia jenis.

Penyedia untuk Protokol Konektivitas Secara umum, nama sebagian besar DLL penyedia untuk protokol konektivitas data dan layanan, seperti koneksi OData atau SQL, harus diakhiri dengan TypeProvider atau TypeProviders. Misalnya, gunakan nama DLL yang menyerupai string berikut ini:

Fabrikam.Management.BasicTypeProviders.dll

Pastikan jenis yang Anda berikan adalah anggota dari namespace layanan yang sesuai, dan tunjukkan protokol konektivitas yang Anda terapkan:

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

Penyedia Utilitas untuk Pengodean Umum. Untuk penyedia jenis utilitas seperti regex, penyedia jenis mungkin menjadi bagian dari pustaka dasar, seperti yang diperlihatkan contoh berikut:

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

Dalam hal ini, jenis yang disediakan akan muncul pada titik yang sesuai menurut konvensi desain .NET normal:

  open Fabrikam.Core.Text.RegexTyped

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

Sumber Data Database Tunggal. Beberapa penyedia jenis terhubung ke satu sumber data khusus dan hanya menyediakan data. Dalam hal ini, Anda harus menghapus 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 akan dijelaskan nanti dalam topik ini.

Pola Desain untuk Penyedia Jenis

Bagian berikut menjelaskan pola desain yang dapat Anda gunakan saat penulisan penyedia jenis.

Pola Desain GetConnection

Sebagian besar penyedia jenis harus ditulis untuk menggunakan pola GetConnection yang digunakan oleh penyedia jenis di FSharp.Data.TypeProviders.dll, seperti yang ditunjukkan contoh berikut:

#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

Jenis Penyedia 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 terhubung. Isu-isu tersebut mencakup pertimbangan berikut:

  • pemetaan skema

  • keaktifan dan pembatalan dengan adanya perubahan skema

  • penembolokan skema

  • implementasi asinkron dari operasi akses data

  • mendukung kueri, termasuk kueri LINQ

  • mandat dan autentikasi

Topik ini tidak mengeksplorasi masalah ini lebih jauh.

Teknik Penulisan Tambahan

Saat Anda menulis penyedia jenis Anda sendiri, Anda mungkin ingin menggunakan teknik tambahan berikut.

Membuat Jenis dan Anggota Sesuai Permintaan

ProvidedType API memiliki versi AddMember yang tertunda.

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

Versi ini digunakan untuk membuat jenis ruang sesuai permintaan.

Menyediakan jenis Array dan Instansiasi Jenis Generik

Anda membuat anggota yang disediakan (yang tanda tangannya mencakup jenis larik, jenis byref, dan instantiasi jenis generik) dengan menggunakan MakeArrayType, MakePointerType, dan MakeGenericType normal pada setiap instans Type, termasuk ProvidedTypeDefinitions.

Catatan

Dalam beberapa kasus, Anda mungkin harus menggunakan pembantu di ProvidedTypeBuilder.MakeGenericType. Lihat Dokumentasi SDK Penyedia Jenis untuk detail selengkapnya.

Menyediakan Anotasi Satuan Ukur

API ProvidedTypes menyediakan pembantu untuk menyediakan anotasi ukuran. 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 memberikan 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 Proyek-Lokal atau Script-Lokal

Setiap instans dari penyedia jenis dapat diberi nilai TypeProviderConfig selama konstruksi. Nilai ini berisi "folder resolusi" untuk penyedia (yaitu, folder proyek untuk kompilasi atau direktori yang berisi skrip), daftar rakitan yang dirujuk, dan informasi lainnya.

Pembatalan

Penyedia dapat meningkatkan sinyal pembatalan untuk memberi tahu layanan bahasa F# bahwa asumsi skema mungkin telah berubah. Saat terjadi pembatalan, pemeriksaan jenis dilakukan ulang jika penyedia dihosting di Visual Studio. Sinyal ini akan diabaikan ketika penyedia di-host di F# Interactive atau oleh F# Compiler (fsc.exe).

Informasi Skema Penembolokan

Penyedia harus sering men-cache akses ke informasi skema. Data cache harus disimpan dengan menggunakan nama file yang diberikan sebagai parameter statis atau sebagai data pengguna. Contoh penembolokan skema adalah parameter LocalSchemaFile di penyedia jenis di rakitan 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 cache, Anda juga harus menyetel parameter statis ForceUpdate ke false. Anda dapat menggunakan teknik serupa untuk mengaktifkan akses data online dan offline.

Rakitan Pendukung

Saat Anda mengompilasi file .dll atau .exe, file .dll pendukung untuk jenis yang dihasilkan ditautkan secara statis ke rakitan yang dihasilkan. Tautan ini dibuat dengan menyalin definisi jenis Bahasa Perantara (IL) dan sumber daya terkelola dari rakitan pendukung ke rakitan akhir. Saat Anda menggunakan F# Interactive, file .dll pendukung tidak disalin dan malah dimuat langsung ke proses F# Interactive.

Pengecualian dan Diagnostik dari Penyedia Jenis

Semua penggunaan semua anggota dari jenis yang disediakan dapat menimbulkan pengecualian. Dalam semua kasus, jika penyedia jenis melempar pengecualian, pengompilasi host mengaitkan kesalahan ke penyedia jenis tertentu.

  • Pengecualian penyedia jenis tidak boleh mengakibatkan kesalahan pengompilasi internal.

  • Penyedia jenis tidak dapat melaporkan peringatan.

  • Ketika penyedia jenis di-host di pengompilasi F#, lingkungan pengembangan F#, atau F# Interactive, semua pengecualian dari penyedia itu ditangkap. Properti Pesan selalu merupakan teks kesalahan, dan tidak ada jejak tumpukan yang muncul. Jika Anda akan melempar pengecualian, Anda dapat membuang contoh berikut: System.NotSupportedException, System.IO.IOException, System.Exception.

Menyediakan Jenis yang Dihasilkan

Sejauh ini, dokumen ini telah menjelaskan cara menyediakan jenis 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 yang disediakan 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:

  • isErased harus diatur ke false.

  • Jenis yang dihasilkan harus ditambahkan ke ProvidedAssembly() yang baru dibuat, yang mewakili wadah untuk fragmen kode yang dihasilkan.

  • Penyedia harus memiliki rakitan yang memiliki file .NET .dll yang sebenarnya dengan file .dll yang cocok pada disk.

Aturan dan Batasan

Saat Anda menulis penyedia jenis, ingatlah aturan dan batasan berikut.

Jenis yang disediakan harus dapat dijangkau

Semua jenis yang disediakan harus dapat dijangkau dari jenis yang tidak bersarang. Jenis non-bersarang diberikan dalam panggilan ke konstruktorTypeProviderForNamespaces atau panggilan ke AddNamespace. Misalnya, jika penyedia menyediakan jenis StaticClass.P : T, Anda harus memastikan bahwa T adalah jenis yang tidak bersarang atau bersarang di bawah satu.

Misalnya, beberapa penyedia memiliki kelas statis seperti DataTypes yang berisi jenis T1, T2, T3, ... ini. Jika tidak, kesalahan mengatakan bahwa referensi ke jenis T di rakitan A ditemukan, tetapi jenisnya tidak dapat ditemukan di rakitan tersebut. Jika kesalahan ini muncul, verifikasi bahwa semua subjenis Anda dapat dijangkau dari jenis penyedia. Catatan: Jenis T1, T2, T3... ini disebut sebagai jenis on-the-fly. Ingatlah untuk meletakkannya di namespace layanan yang dapat diakses atau jenis induk.

Keterbatasan Mekanisme Penyedia Jenis

Mekanisme penyedia jenis di F# memiliki batasan sebagai berikut:

  • Infrastruktur dasar untuk penyedia jenis di F# tidak mendukung jenis generik yang disediakan atau metode generik yang disediakan.

  • Mekanisme tidak mendukung jenis bersarang dengan parameter statis.

Kiat Pengembangan

Anda mungkin menemukan tips berikut membantu selama proses pengembangan:

Jalankan dua contoh Visual Studio

Anda dapat mengembangkan penyedia jenis di satu contoh dan menguji penyedia di contoh lain karena IDE pengujian akan mengunci file .dll yang mencegah penyedia jenis dibangun kembali. Jadi, Anda harus menutup instans kedua dari Visual Studio saat penyedia dibangun di instans pertama, dan kemudian Anda harus membuka kembali instans kedua setelah penyedia dibangun.

Debug penyedia jenis dengan menggunakan doa fsc.exe

Anda dapat memanggil penyedia jenis dengan menggunakan alat berikut:

  • fsc.exe (Pengompilasi baris perintah F#)

  • fsi.exe (Pengompilasi F# Interactive)

  • 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 perintah.

devenv /debugexe fsc.exe script.fsx

Anda dapat menggunakan pengelogan print-to-stdout.

Lihat juga