次の方法で共有


チュートリアル: 型プロバイダーを作成する

F# の型プロバイダー メカニズムは、情報豊富なプログラミングのサポートの重要な部分です。 このチュートリアルでは、いくつかの単純な型プロバイダーの開発について説明し、基本的な概念を説明することで、独自の型プロバイダーを作成する方法について説明します。 F# の型プロバイダー メカニズムの詳細については、「 型プロバイダー」を参照してください。

F# エコシステムには、一般的に使用されるインターネットおよびエンタープライズ データ サービス用のさまざまな型プロバイダーが含まれています。 例えば次が挙げられます。

  • FSharp.Data には、JSON、XML、CSV、HTML ドキュメント形式の型プロバイダーが含まれています。

  • SwaggerProvider には、OpenApi 3.0 および Swagger 2.0 スキーマで記述された API 用のオブジェクト モデルと HTTP クライアントを生成する 2 つの生成型プロバイダーが含まれています。

  • FSharp.Data.SqlClient には、F# で T-SQL をコンパイル時にチェックして埋め込むための型プロバイダーのセットがあります。

カスタム型プロバイダーを作成することも、他のユーザーが作成した型プロバイダーを参照することもできます。 たとえば、組織には、それぞれが独自の安定したデータ スキーマを持つ、多数の名前付きデータ セットを提供するデータ サービスを用意できます。 スキーマを読み取り、現在のデータ セットを厳密に型指定された方法でプログラマに提示する型プロバイダーを作成できます。

開始前の準備

型プロバイダー メカニズムは、主に、F# プログラミング エクスペリエンスに安定したデータとサービス情報スペースを挿入するように設計されています。

このメカニズムは、プログラムロジックに関連する方法でプログラムの実行中にスキーマが変更される情報スペースを挿入するために設計されていません。 また、このメカニズムは、そのドメインに有効な用途が含まれている場合でも、言語内メタプログラミング用に設計されていません。 このメカニズムは、必要な場合と、型プロバイダーの開発によって非常に高い値が生成される場合にのみ使用する必要があります。

スキーマを使用できない型プロバイダーの記述は避ける必要があります。 同様に、通常の (または既存の) .NET ライブラリで十分な型プロバイダーを記述しないようにする必要があります。

開始する前に、次の質問をする場合があります。

  • 情報ソースのスキーマはありますか? その場合、F# および .NET 型システムへのマッピングは何ですか?

  • 既存の (動的に型指定された) API を実装の開始点として使用できますか?

  • ユーザーと組織は、型プロバイダーを十分に使用して、書き込みを価値のあるものにしますか? 通常の .NET ライブラリはニーズを満たしますか?

  • スキーマはどのくらい変更されますか?

  • コーディング中に変更されますか?

  • コーディング セッション間で変更されますか?

  • プログラムの実行中に変更されますか?

型プロバイダーは、実行時およびコンパイル済みコードの有効期間中にスキーマが安定している状況に最適です。

単純型プロバイダー

このサンプルは Samples.HelloWorldTypeProvider で、F# Type Provider SDKexamples ディレクトリ内のサンプルと同様です。 次のコードでは F# シグネチャ構文を使用し、 Type1を除くすべての詳細を省略することで示すように、プロバイダーは 100 個の消去された型を含む "型空間" を使用できるようにします。 消去された型の詳細については、このトピックで後述する「 消去された指定された型の詳細 」を参照してください。

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 =
…

指定された型とメンバーのセットは静的に認識されることに注意してください。 この例では、プロバイダーがスキーマに依存する型を提供する機能を利用しません。 型プロバイダーの実装については、次のコードで説明します。詳細については、このトピックの後のセクションで説明します。

Warnung

このコードとオンライン サンプルには違いがある可能性があります。

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()

このプロバイダーを使用するには、Visual Studio の別のインスタンスを開き、F# スクリプトを作成し、次のコードに示すように #r を使用してスクリプトからプロバイダーへの参照を追加します。

#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

次に、型プロバイダーによって生成された Samples.HelloWorldTypeProvider 名前空間の下にある型を探します。

プロバイダーを再コンパイルする前に、プロバイダー DLL を使用している Visual Studio と F# Interactive のすべてのインスタンスを閉じていることを確認します。 そうしないと、出力 DLL がロックされるため、ビルド エラーが発生します。

print ステートメントを使用してこのプロバイダーをデバッグするには、プロバイダーの問題を公開するスクリプトを作成し、次のコードを使用します。

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

Visual Studio を使用してこのプロバイダーをデバッグするには、管理者資格情報を使用して Visual Studio の開発者コマンド プロンプトを開き、次のコマンドを実行します。

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

別の方法として、Visual Studio を開き、[デバッグ] メニューを開き、[ Debug/Attach to process…] を選択し、スクリプトを編集している別の devenv プロセスにアタッチします。 このメソッドを使用すると、2 番目のインスタンスに式を対話形式で入力することで (完全な IntelliSense やその他の機能を使用して) 型プロバイダーの特定のロジックをより簡単にターゲットにすることができます。

マイ コードのみデバッグを無効にして、生成されたコードのエラーをより適切に識別できます。 この機能を有効または無効にする方法については、「デバッガーを使用 したコード間の移動」を参照してください。 また、 Debug メニューを開き、[ Exceptions ] を選択するか、Ctrl + Alt + E キーを押して [ Exceptions ] ダイアログ ボックスを開くことで、一時例外のキャッチを設定することもできます。 そのダイアログ ボックスの [ Common Language Runtime Exceptions] で、[ Thrown ] チェック ボックスをオンにします。

型プロバイダーの実装

このセクションでは、型プロバイダーの実装の主要なセクションについて説明します。 最初に、カスタム型プロバイダー自体の型を定義します。

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

この型はパブリックである必要があります。また、型を含むアセンブリを別の F# プロジェクトが参照するときにコンパイラが型プロバイダーを認識できるように、 TypeProvider 属性でマークする必要があります。 config パラメーターは省略可能であり、存在する場合は、F# コンパイラによって作成される型プロバイダー インスタンスのコンテキスト構成情報が含まれます。

次に、 ITypeProvider インターフェイスを 実装します。 この場合は、ProvidedTypes API のTypeProviderForNamespaces型を基本型として使用します。 このヘルパー型は、一括して提供される名前空間の有限のコレクションを提供できます。各名前空間には、固定された、熱心に提供される型の有限の数が直接含まれています。 このコンテキストでは、プロバイダーは、必要または使用されていない場合でも、型を 熱心 に生成します。

inherit TypeProviderForNamespaces(config)

次に、指定された型の名前空間を指定するローカル プライベート値を定義し、型プロバイダー アセンブリ自体を見つけます。 このアセンブリは、後で提供される消去型の論理親型として使用されます。

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

次に、Type1 の各型を指定する関数を作成します。Type100。 この関数については、このトピックの後半で詳しく説明します。

let makeOneProvidedType (n:int) = …

次に、100 個の指定された型を生成します。

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

次に、指定された名前空間として型を追加します。

do this.AddNamespace(namespaceName, types)

最後に、型プロバイダー DLL を作成することを示すアセンブリ属性を追加します。

[<assembly:TypeProviderAssembly>]
do()

1 つの型とそのメンバーを指定する

makeOneProvidedType関数は、型のいずれかを提供する実際の作業を行います。

let makeOneProvidedType (n:int) =
…

この手順では、この関数の実装について説明します。 最初に、指定された型を作成します (たとえば、Type1 (n = 1 の場合) または Type57 (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>)

次の点に注意してください。

  • この指定された型は消去されます。 基本型が objであることを示しているため、インスタンスはコンパイル済みコードの obj 型の値として表示されます。

  • 入れ子になっていない型を指定する場合は、アセンブリと名前空間を指定する必要があります。 消去された型の場合、アセンブリは型プロバイダー アセンブリ自体である必要があります。

次に、XML ドキュメントを型に追加します。 このドキュメントは遅延されます。つまり、ホスト コンパイラで必要な場合はオンデマンドで計算されます。

t.AddXmlDocDelayed (fun () -> $"""This provided type {"Type" + string n}""")

次に、指定された静的プロパティを型に追加します。

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

このプロパティを取得すると、常に文字列 "Hello!" に評価されます。 プロパティの GetterCode は F# 引用符を使用します。これは、ホスト コンパイラがプロパティを取得するために生成するコードを表します。 見積の詳細については、「 コード引用 (F#)」を参照してください。

プロパティに XML ドキュメントを追加します。

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

次に、指定したプロパティを指定された型にアタッチします。 指定されたメンバーを 1 つだけの型にアタッチする必要があります。 それ以外の場合、メンバーにアクセスすることはできません。

t.AddMember staticProp

次に、パラメーターを受け取っていない指定されたコンストラクターを作成します。

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

コンストラクターの InvokeCode は F# 引用符を返します。これは、コンストラクターが呼び出されたときにホスト コンパイラによって生成されるコードを表します。 たとえば、次のコンストラクターを使用できます。

new Type10()

指定された型のインスタンスは、基になるデータ "オブジェクト データ" で作成されます。 引用符で囲まれたコードには obj への変換が含まれます。これは、その型がこの指定された型の消去であるためです (指定された型を宣言したときに指定したとおり)。

XML ドキュメントをコンストラクターに追加し、指定されたコンストラクターを指定された型に追加します。

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

t.AddMember ctor

1 つのパラメーターを受け取る 2 つ目の指定されたコンストラクターを作成します。

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

コンストラクターの InvokeCode は、メソッドの呼び出しに対してホスト コンパイラによって生成されたコードを表す F# 引用符を再び返します。 たとえば、次のコンストラクターを使用できます。

new Type10("ten")

指定された型のインスタンスは、基になるデータ "ten" で作成されます。 InvokeCode関数が引用符を返していることに既に気付いているかもしれません。 この関数への入力は、コンストラクター パラメーターごとに 1 つずつ、式の一覧です。 この場合、単一のパラメーター値を表す式を args[0]で使用できます。 コンストラクターの呼び出しのコードは、戻り値を消去された型 obj強制します。 2 番目に指定されたコンストラクターを型に追加した後、指定されたインスタンス プロパティを作成します。

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

このプロパティを取得すると、文字列の長さ (表現オブジェクト) が返されます。 GetterCode プロパティは、ホスト コンパイラがプロパティを取得するために生成するコードを指定する F# 引用符を返します。 InvokeCodeと同様に、GetterCode関数は引用符を返します。 ホスト コンパイラは、引数の一覧を使用してこの関数を呼び出します。 この場合、引数には、getter が呼び出されるインスタンスを表す単一の式だけが含まれ、 args[0]を使用してアクセスできます。 GetterCodeの実装は、消去された型objで結果の引用符にスプライスし、キャストは、オブジェクトが文字列であることを型をチェックするためのコンパイラのメカニズムを満たすために使用されます。 makeOneProvidedTypeの次の部分では、1 つのパラメーターを持つインスタンス メソッドを提供します。

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

最後に、100 個の入れ子になったプロパティを含む入れ子になった型を作成します。 この入れ子になった型とそのプロパティの作成は遅延されます。つまり、オンデマンドで計算されます。

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

消去された指定された型の詳細

このセクションの例では、 消去された指定された型のみを提供します。これは、次の状況で特に役立ちます。

  • データとメソッドのみを含む情報空間のプロバイダーを作成する場合。

  • 正確なランタイム型セマンティクスが情報空間の実用的な使用にとって重要ではないプロバイダーを記述する場合。

  • 非常に大きく相互接続された情報空間のプロバイダーを作成する場合、情報空間に実際の .NET 型を生成することは技術的には不可能です。

この例では、指定された各型は obj型に消去され、型のすべての使用はコンパイル済みコードで型 obj として表示されます。 実際、これらの例の基になるオブジェクトは文字列ですが、.NET コンパイル 済みコードでは型は System.Object として表示されます。 型消去のすべての使用と同様に、明示的なボックス化、ボックス化解除、キャストを使用して、消去された型を覆すことができます。 この場合、オブジェクトを使用すると、無効なキャスト例外が発生する可能性があります。 プロバイダー ランタイムは、独自のプライベート表現型を定義して、誤った表現から保護できます。 F# 自体で消去型を定義することはできません。 指定された型のみが消去されます。 型プロバイダーまたは消去型を提供するプロバイダーに対して消去型を使用する場合の、実用的な影響とセマンティック性の両方を理解する必要があります。 消去された型には、実際の .NET 型はありません。 そのため、型に対して正確なリフレクションを行うことはできません。また、ランタイム キャストや、正確なランタイム型セマンティクスに依存するその他の手法を使用すると、消去された型が変換される可能性があります。 消去された型のサブバージョンでは、実行時に型キャスト例外が発生する場合がよくあります。

消去された指定された型の表現の選択

消去された指定された型の一部の使用では、表現は必要ありません。 たとえば、消去された指定された型には静的プロパティとメンバーのみが含まれる場合があり、コンストラクターは含まず、型のインスタンスを返すメソッドやプロパティはありません。 消去された指定された型のインスタンスに到達できる場合は、次の質問を考慮する必要があります。

指定された型の消去とは何ですか?

  • 指定された型の消去は、コンパイル済みの .NET コードでの型の表示方法です。

  • 指定された消去されたクラス型の消去は、常に型の継承チェーン内の最初の非消去基本型です。

  • 提供された消去インターフェイスの種類の消去は常に System.Object

指定された型の表現は何ですか?

  • 消去された指定された型に対して可能なオブジェクトのセットは、その表現と呼ばれます。 このドキュメントの例では、 Type1..Type100 に指定されたすべての消去された型の表現は常に文字列オブジェクトです。

指定された型のすべての表現は、指定された型の消去と互換性がある必要があります。 (それ以外の場合、F# コンパイラによって型プロバイダーの使用に関するエラーが発生するか、無効な検証不可能な .NET コードが生成されます。型プロバイダーは、無効な表現を提供するコードを返す場合は無効です)。

次のいずれかの方法を使用して、指定されたオブジェクトの表現を選択できます。どちらも非常に一般的です。

  • 既存の .NET 型に対して厳密に型指定されたラッパーを提供するだけの場合、多くの場合、型がその型に消去されるか、その型のインスタンスを表現として使用するか、またはその両方として使用するのが理にかなっています。 この方法は、厳密に型指定されたバージョンを使用する場合に、その型の既存のメソッドの大部分がまだ意味を持つ場合に適しています。

  • 既存の .NET API とは大きく異なる API を作成する場合は、指定された型の型の消去と表現となるランタイム型を作成するのが理にかなっています。

このドキュメントの例では、指定されたオブジェクトの表現として文字列を使用します。 多くの場合、他のオブジェクトを表現に使用することが適切な場合があります。 たとえば、プロパティ バッグとしてディクショナリを使用できます。

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

別の方法として、1 つ以上のランタイム操作と共に、実行時に表現を形成するために使用される型プロバイダーで型を定義することもできます。

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

指定されたメンバーは、次のオブジェクト型のインスタンスを構築できます。

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

この場合、(必要に応じて) この型を型消去として使用できます。この型は、ProvidedTypeDefinitionを構築するときにbaseTypeとして指定します。

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

主要なレッスン

前のセクションでは、さまざまな型、プロパティ、メソッドを提供する単純な消去型プロバイダーを作成する方法について説明しました。 このセクションでは、型プロバイダーから消去型を提供する利点と欠点の一部を含む、型消去の概念について説明し、消去された型の表現についても説明しました。

静的パラメーターを使用する型プロバイダー

静的データによって型プロバイダーをパラメーター化する機能により、プロバイダーがローカルまたはリモートのデータにアクセスする必要がない場合でも、多くの興味深いシナリオが可能になります。 このセクションでは、このようなプロバイダーをまとめるための基本的な手法について説明します。

Type Checked Regex Provider

次のコンパイル時の保証を提供するインターフェイスで .NET Regex ライブラリをラップする正規表現の型プロバイダーを実装するとします。

  • 正規表現が有効かどうかを確認します。

  • 正規表現内の任意のグループ名に基づく一致に名前付きプロパティを指定します。

このセクションでは、型プロバイダーを使用して、正規表現パターンがパラメーター化してこれらの利点を提供する RegexTyped 型を作成する方法について説明します。 指定されたパターンが有効でない場合、コンパイラはエラーを報告し、型プロバイダーはパターンからグループを抽出して、一致する名前付きプロパティを使用してアクセスできるようにします。 型プロバイダーを設計するときは、公開されている API がエンド ユーザーにどのように見えるのか、この設計が .NET コードにどのように変換されるかを検討する必要があります。 次の例は、このような API を使用して、エリア コードのコンポーネントを取得する方法を示しています。

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"

次の例は、型プロバイダーがこれらの呼び出しを変換する方法を示しています。

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"

次の点に注意してください。

  • 標準の Regex 型は、パラメーター化された RegexTyped 型を表します。

  • RegexTyped コンストラクターは Regex コンストラクターを呼び出し、パターンの静的な型引数を渡します。

  • Match メソッドの結果は、標準のMatch型で表されます。

  • 名前付きグループごとに指定されたプロパティが作成され、プロパティにアクセスすると、一致する Groups コレクションでインデクサーが使用されます。

次のコードは、このようなプロバイダーを実装するためのロジックの中核であり、この例では、指定された型へのすべてのメンバーの追加を省略しています。 追加された各メンバーの詳細については、このトピックで後述する適切なセクションを参照してください。

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 ()

次の点に注意してください。

  • 型プロバイダーは 2 つの静的パラメーターを受け取ります。必須の patternと、省略可能な options (既定値が指定されているため)。

  • 静的引数を指定した後、正規表現のインスタンスを作成します。 Regex の形式が正しくない場合、このインスタンスは例外をスローし、このエラーはユーザーに報告されます。

  • DefineStaticParametersコールバック内で、引数の指定後に返される型を定義します。

  • このコードは HideObjectMethods を true に設定して、IntelliSense エクスペリエンスを合理化し続けます。 この属性により、指定されたオブジェクトの IntelliSense リストから EqualsGetHashCodeFinalize、および GetType メンバーが非表示になります。

  • objをメソッドの基本型として使用しますが、次の例に示すように、この型のランタイム表現としてRegex オブジェクトを使用します。

  • 正規表現が有効でない場合、 Regex コンストラクターの呼び出しによって ArgumentException がスローされます。 コンパイラはこの例外をキャッチし、コンパイル時または Visual Studio エディターでエラー メッセージをユーザーに報告します。 この例外により、アプリケーションを実行せずに正規表現を検証できます。

上記で定義した型は、意味のあるメソッドやプロパティが含まれていないため、まだ役に立ちません。 まず、静的 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

前のコードでは、文字列を入力として受け取り、boolを返すメソッド IsMatchを定義しています。 唯一の難しい部分は、InvokeCode定義内でargs引数を使用することです。 この例では、 args は、このメソッドの引数を表す引用符の一覧です。 メソッドがインスタンス メソッドの場合、最初の引数は this 引数を表します。 ただし、静的メソッドの場合、引数はすべてメソッドに対する明示的な引数にすぎません。 引用符で囲まれた値の型は、指定された戻り値の型 (この場合は bool) と一致する必要があることに注意してください。 また、このコードでは、 AddXmlDoc メソッドを使用して、提供されたメソッドにも役立つドキュメントがあることを確認します。このドキュメントは IntelliSense を通じて提供できます。

次に、インスタンス Match メソッドを追加します。 ただし、このメソッドは、厳密に型指定された方法でグループにアクセスできるように、指定された Match 型の値を返す必要があります。 したがって、最初に Match 型を宣言します。 この型は静的引数として指定されたパターンに依存するため、この型はパラメーター化された型定義内で入れ子にする必要があります。

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

ty.AddMember matchTy

次に、各グループの Match 型に 1 つのプロパティを追加します。 実行時に、一致は Match 値として表されるため、プロパティを定義する引用符は、 Groups インデックス付きプロパティを使用して関連するグループを取得する必要があります。

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

ここでも、指定されたプロパティに XML ドキュメントを追加します。 また、 GetterCode 関数が指定されている場合はプロパティを読み取ることができ、 SetterCode 関数が指定されている場合はプロパティを書き込むことができるため、結果のプロパティは読み取り専用になります。

これで、この 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

インスタンス メソッドを作成するため、 args[0] はメソッドが呼び出されている RegexTyped インスタンスを表し、 args[1] は入力引数です。

最後に、指定された型のインスタンスを作成できるように、コンストラクターを指定します。

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

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

ty.AddMember ctor

コンストラクターは、標準の .NET Regex インスタンスの作成を消去するだけです。これは、 obj が指定された型の消去であるため、オブジェクトに再びボックス化されます。 この変更により、トピックで前に指定したサンプル API の使用が想定どおりに動作します。 次のコードは完全で最終的なコードです。

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 ()

主要なレッスン

このセクションでは、静的パラメーターを操作する型プロバイダーを作成する方法について説明しました。 プロバイダーは静的パラメーターをチェックし、その値に基づいて操作を提供します。

ローカル データによってサポートされる型プロバイダー

多くの場合、型プロバイダーは、静的パラメーターだけでなく、ローカル またはリモート システムからの情報に基づいて API を提示することが必要になる場合があります。 このセクションでは、ローカル データ ファイルなどのローカル データに基づく型プロバイダーについて説明します。

単純な CSV ファイル プロバイダー

簡単な例として、コンマ区切り値 (CSV) 形式で科学データにアクセスするための型プロバイダーを考えてみましょう。 このセクションでは、次の表に示すように、CSV ファイルにヘッダー行とそれに続く浮動小数点データが含まれていることを前提としています。

距離 (メーター) 時間 (秒)
50.0 3.7
100.0 5.2
150.0 6.4

このセクションでは、float<meter>型のDistance プロパティとfloat<second>型のTime プロパティを持つ行を取得するために使用できる型を指定する方法について説明します。 わかりやすくするために、次の前提条件が行われます。

  • ヘッダー名は単位なし、または "Name (unit)" という形式で、コンマは含まれません。

  • 単位はすべて、 FSharp.Data.UnitSystems.SI.UnitNames モジュール (F#) モジュール が定義するシステム インターナショナル (SI) 単位です。

  • 単位はすべて、複合 (メートル/秒など) ではなく単純です (例: メーター)。

  • すべての列に浮動小数点データが含まれています。

より完全なプロバイダーは、これらの制限を緩和します。

ここでも最初の手順は、API の外観を検討することです。 前の表の内容を含む info.csv ファイル (コンマ区切り形式) を指定すると、プロバイダーのユーザーは次の例のようなコードを記述できる必要があります。

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

この場合、コンパイラはこれらの呼び出しを次の例のように変換する必要があります。

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

最適な変換では、型プロバイダーが型プロバイダーのアセンブリで実際の CsvFile 型を定義する必要があります。 型プロバイダーは、多くの場合、重要なロジックをラップするために、いくつかのヘルパー型とメソッドに依存します。 メジャーは実行時に消去されるため、行の消去型として float[] を使用できます。 コンパイラは、異なる列を異なるメジャーの種類として扱います。 たとえば、この例の最初の列には型 float<meter>があり、2 番目の列には float<second>があります。 ただし、消去された表現は非常に単純なままです。

次のコードは、実装の中核を示しています。

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

実装に関する次の点に注意してください。

  • オーバーロードされたコンストラクターを使用すると、元のファイルまたは同じスキーマを持つファイルを読み取れます。 このパターンは、ローカル データ ソースまたはリモート データ ソース用の型プロバイダーを記述する場合に一般的であり、このパターンでは、ローカル ファイルをリモート データのテンプレートとして使用できます。

  • 型プロバイダー コンストラクターに渡される TypeProviderConfig 値を使用して、相対ファイル名を解決できます。

  • AddDefinitionLocation メソッドを使用して、指定されたプロパティの場所を定義できます。 そのため、指定したプロパティで Go To Definition を使用すると、VISUAL Studio で CSV ファイルが開きます。

  • ProvidedMeasureBuilderタイプを使用して、SI 単位を検索し、関連するfloat<_>タイプを生成できます。

主要なレッスン

このセクションでは、データ ソース自体に含まれる単純なスキーマを使用して、ローカル データ ソースの型プロバイダーを作成する方法について説明しました。

さらに進む

以降のセクションでは、さらに詳しい調査を行う際の推奨事項を示します。

消去された型のコンパイル済みコードを見る

型プロバイダーの使用が出力されるコードにどのように対応するかを把握するには、このトピックで前に使用した HelloWorldTypeProvider を使用して、次の関数を参照してください。

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

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

この例に示すように、 Type1 型と InstanceProperty プロパティのすべてのメンションは消去され、関連するランタイム型に対する操作のみが残されています。

型プロバイダーの設計規則と名前付け規則

型プロバイダーを作成するときは、次の規則に従います。

接続プロトコルのプロバイダー 一般に、OData や SQL 接続などのデータおよびサービス接続プロトコルのほとんどのプロバイダー DLL の名前は、 TypeProvider または TypeProvidersで終わる必要があります。 たとえば、次の文字列のような DLL 名を使用します。

Fabrikam.Management.BasicTypeProviders.dll

指定した型が対応する名前空間のメンバーであることを確認し、実装した接続プロトコルを示します。

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

一般的なコーディングのためのユーティリティ プロバイダー。 正規表現などのユーティリティ型プロバイダーの場合、次の例に示すように、型プロバイダーは基本ライブラリの一部である可能性があります。

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

この場合、指定された型は、通常の .NET 設計規則に従って適切なポイントに表示されます。

  open Fabrikam.Core.Text.RegexTyped

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

シングルトン データ ソース。 一部の型プロバイダーは、単一の専用データ ソースに接続し、データのみを提供します。 この場合は、 TypeProvider サフィックスを削除し、.NET の名前付けに通常の規則を使用する必要があります。

#r "Fabrikam.Data.Freebase.dll"

let data = Fabrikam.Data.Freebase.Astronomy.Asteroids

詳細については、このトピックで後述する GetConnection 設計規則を参照してください。

型プロバイダーの設計パターン

次のセクションでは、型プロバイダーを作成するときに使用できるデザイン パターンについて説明します。

GetConnection デザイン パターン

次の例に示すように、ほとんどの型プロバイダーは、FSharp.Data.TypeProviders.dllの型プロバイダーによって使用される GetConnection パターンを使用するように記述する必要があります。

#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

リモート データとサービスによってサポートされる型プロバイダー

リモート データとサービスによってサポートされる型プロバイダーを作成する前に、接続されたプログラミングに固有のさまざまな問題を考慮する必要があります。 これらの問題には、次の考慮事項が含まれます。

  • スキーマ マッピング

  • スキーマの変更が存在する場合のライブネスと無効化

  • スキーマ キャッシュ

  • データ アクセス操作の非同期実装

  • LINQ クエリを含むサポート クエリ

  • 資格情報と認証

このトピックでは、これらの問題について詳しく説明しません。

その他の作成手法

独自の型プロバイダーを記述する場合は、次の追加の手法を使用できます。

オンデマンドでの型とメンバーの作成

ProvidedType API には、AddMember の遅延バージョンがあります。

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

これらのバージョンは、型のオンデマンド空間を作成するために使用されます。

配列型とジェネリック型のインスタンス化の提供

指定されたメンバー (配列型、byref 型、ジェネリック型のインスタンス化を含むシグネチャ) は、ProvidedTypeDefinitionsを含む、Typeの任意のインスタンスで通常のMakeArrayTypeMakePointerType、およびMakeGenericTypeを使用して行います。

場合によっては、 ProvidedTypeBuilder.MakeGenericTypeでヘルパーを使用する必要があります。 詳細については、 Type Provider SDK のドキュメント を参照してください。

測定単位注釈の提供

ProvidedTypes API には、メジャー注釈を提供するためのヘルパーが用意されています。 たとえば、型 float<kg>を指定するには、次のコードを使用します。

  let measures = ProvidedMeasureBuilder.Default
  let kg = measures.SI "kilogram"
  let m = measures.SI "meter"
  let float_kg = measures.AnnotateType(typeof<float>,[kg])

Nullable<decimal<kg/m^2>>を指定するには、次のコードを使用します。

  let kgpm2 = measures.Ratio(kg, measures.Square m)
  let dkgpm2 = measures.AnnotateType(typeof<decimal>,[kgpm2])
  let nullableDecimal_kgpm2 = typedefof<System.Nullable<_>>.MakeGenericType [|dkgpm2 |]

Project-Local または Script-Local リソースへのアクセス

型プロバイダーの各インスタンスには、構築時に TypeProviderConfig 値を指定できます。 この値には、プロバイダーの "解決フォルダー" (つまり、コンパイル用のプロジェクト フォルダーまたはスクリプトを含むディレクトリ)、参照されるアセンブリの一覧、およびその他の情報が含まれます。

無効

プロバイダーは無効化シグナルを発生させ、スキーマの前提条件が変更された可能性があることを F# 言語サービスに通知できます。 無効化が発生すると、プロバイダーが Visual Studio でホストされている場合、型チェックは再実行されます。 プロバイダーが F# Interactive または F# コンパイラ (fsc.exe) でホストされている場合、このシグナルは無視されます。

スキーマ情報のキャッシュ

プロバイダーは、多くの場合、スキーマ情報へのアクセスをキャッシュする必要があります。 キャッシュされたデータは、静的パラメーターまたはユーザー データとして指定されたファイル名を使用して格納する必要があります。 スキーマ キャッシュの例として、FSharp.Data.TypeProviders アセンブリの型プロバイダーのLocalSchemaFile パラメーターがあります。 これらのプロバイダーの実装では、この静的パラメーターは、ネットワーク経由でデータ ソースにアクセスする代わりに、指定したローカル ファイル内のスキーマ情報を使用するように型プロバイダーに指示します。 キャッシュされたスキーマ情報を使用するには、静的パラメーター ForceUpdatefalse に設定する必要もあります。 同様の手法を使用して、オンラインとオフラインのデータ アクセスを有効にすることができます。

バッキング アセンブリ

.dllまたは.exe ファイルをコンパイルすると、生成された型のバッキング .dll ファイルが結果のアセンブリに静的にリンクされます。 このリンクは、中間言語 (IL) 型定義とすべてのマネージド リソースをバッキング アセンブリから最終的なアセンブリにコピーすることによって作成されます。 F# Interactive を使用すると、バッキング .dll ファイルはコピーされず、代わりに F# 対話型プロセスに直接読み込まれます。

型プロバイダーからの例外と診断

指定された型のすべてのメンバーを使用すると、例外がスローされる可能性があります。 いずれの場合も、型プロバイダーが例外をスローした場合、ホスト コンパイラはエラーを特定の型プロバイダーに属性付けします。

  • 型プロバイダーの例外によって内部コンパイラ エラーが発生することはありません。

  • 型プロバイダーは警告を報告できません。

  • 型プロバイダーが F# コンパイラ、F# 開発環境、または F# Interactive でホストされている場合、そのプロバイダーのすべての例外がキャッチされます。 Message プロパティは常にエラー テキストであり、スタック トレースは表示されません。 例外をスローする場合は、 System.NotSupportedExceptionSystem.IO.IOExceptionSystem.Exceptionの例をスローできます。

生成された型の提供

ここまで、このドキュメントでは消去型を提供する方法について説明しました。 F# の型プロバイダー メカニズムを使用して、生成された型を提供することもできます。生成された型は、実際の .NET 型定義としてユーザーのプログラムに追加されます。 型定義を使用して、生成された指定された型を参照する必要があります。

open Microsoft.FSharp.TypeProviders

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

F# 3.0 リリースの一部である ProvidedTypes-0.2 ヘルパー コードでは、生成された型を提供するためのサポートは制限されています。 次のステートメントは、生成された型定義に対して true である必要があります。

  • isErasedfalse に設定されていること。

  • 生成された型は、生成されたコード フラグメントのコンテナーを表す新しく構築された ProvidedAssembly()に追加する必要があります。

  • プロバイダーには、ディスク上の一致する .dll ファイルを持つ実際のバッキング .NET .dll ファイルを持つアセンブリが必要です。

規則と制限事項

型プロバイダーを記述するときは、次の規則と制限事項に留意してください。

指定された型に到達可能である必要があります

指定されたすべての型は、入れ子になっていない型から到達可能である必要があります。 入れ子になっていない型は、 TypeProviderForNamespaces コンストラクターの呼び出しまたは AddNamespaceの呼び出しで指定されます。 たとえば、プロバイダーが型 StaticClass.P : Tを提供する場合は、T が入れ子になっていない型であるか、1 つ下に入れ子になっていることを確認する必要があります。

たとえば、一部のプロバイダーには、これらのT1, T2, T3, ...型を含むDataTypesなどの静的クラスがあります。 それ以外の場合は、アセンブリ A の T 型への参照が見つかりましたが、そのアセンブリでその型が見つかりませんでした。 このエラーが表示された場合は、プロバイダーの種類からすべてのサブタイプに到達できることを確認します。 注: これらの T1, T2, T3... タイプはオン ザフライ タイプと呼ばれます。 アクセス可能な名前空間または親型に配置することを忘れないでください。

型プロバイダー メカニズムの制限事項

F# の型プロバイダー メカニズムには、次の制限があります。

  • F# の型プロバイダーの基になるインフラストラクチャでは、提供されたジェネリック型または提供されたジェネリック メソッドはサポートされていません。

  • このメカニズムでは、静的パラメーターを持つ入れ子になった型はサポートされていません。

開発に関するヒント

開発プロセス中に、次のヒントが役立つ場合があります。

Visual Studio の 2 つのインスタンスを実行する

1 つのインスタンスで型プロバイダーを開発し、もう一方のインスタンスでプロバイダーをテストできます。これは、テスト IDE が .dll ファイルをロックして、型プロバイダーが再構築されないようにするためです。 そのため、プロバイダーが最初のインスタンスにビルドされている間に Visual Studio の 2 番目のインスタンスを閉じる必要があります。次に、プロバイダーのビルド後に 2 番目のインスタンスを再度開く必要があります。

fsc.exe の呼び出しを使用して型プロバイダーをデバッグする

次のツールを使用して、型プロバイダーを呼び出すことができます。

  • fsc.exe (F# コマンド ライン コンパイラ)

  • fsi.exe (F# 対話型コンパイラ)

  • devenv.exe (Visual Studio)

多くの場合、テスト スクリプト ファイル (script.fsx など) で fsc.exe を使用して、型プロバイダーを最も簡単にデバッグできます。 コマンド プロンプトからデバッガーを起動できます。

devenv /debugexe fsc.exe script.fsx

stdout への出力ログを使用できます。

こちらも参照ください