Руководство по созданию поставщика типов

Механизм поставщика типов в F# является важной частью поддержки расширенного программирования информации. В этом руководстве объясняется, как создать собственные поставщики типов, пройдя разработку нескольких простых поставщиков типов, чтобы проиллюстрировать основные понятия. Дополнительные сведения о механизме поставщика типов в F#см. в разделе "Поставщики типов".

Экосистема F# содержит ряд поставщиков типов для часто используемых служб данных Интернета и корпоративных данных. Например:

  • FSharp.Data включает поставщиков типов для форматов документов JSON, XML, CSV и HTML.

  • SwaggerProvider включает в себя два поставщика формируемых типов, которые создают объектную модель и HTTP-клиенты для API, описанные в схемах OpenApi 3.0 и Swagger 2.0.

  • FSharp.Data.SqlClient имеет набор поставщиков типов для проверки внедрения T-SQL во время компиляции в F#.

Вы можете создавать поставщики пользовательских типов или ссылаться на поставщики типов, созданные другими пользователями. Например, ваша организация может иметь службу данных, которая предоставляет большое и растущее число именованных наборов данных, каждая из которых имеет собственную стабильную схему данных. Вы можете создать поставщик типов, который считывает схемы и представляет текущие наборы данных программисту строго типизированным образом.

Прежде чем начать

Механизм поставщика типов в основном предназначен для внедрения стабильных данных и информационных пространств службы в интерфейс программирования F#.

Этот механизм не предназначен для внедрения информационных пространств, схемы которых изменяются во время выполнения программы способами, соответствующими логике программы. Кроме того, механизм не предназначен для метапрограммного программирования на языке, даже если этот домен содержит некоторые допустимые способы использования. Этот механизм следует использовать только в тех случаях, когда необходимо и где разработка поставщика типов дает очень высокую ценность.

Следует избегать написания поставщика типов, в котором недоступна схема. Аналогичным образом следует избегать написания поставщика типов, где достаточно обычной (или даже существующей) библиотеки .NET.

Перед началом работы можно задать следующие вопросы:

  • У вас есть схема для источника информации? Если да, что такое сопоставление в системе типов F# и .NET?

  • Можно ли использовать существующий (динамически типизированный) API в качестве отправной точки для реализации?

  • Будет ли у вас и вашей организации достаточно использования поставщика типов, чтобы сделать его нужным? Будет ли обычная библиотека .NET соответствовать вашим потребностям?

  • Сколько изменится схема?

  • Изменится ли оно во время написания кода?

  • Изменится ли оно между сеансами кодирования?

  • Изменится ли оно во время выполнения программы?

Поставщики типов лучше всего подходят для ситуаций, когда схема стабильна во время выполнения и во время существования скомпилированного кода.

Поставщик простых типов

Этот пример — Samples.HelloWorldTypeProvider, аналогичный примерам в каталоге examplesпакета SDK поставщика типов F#. Поставщик предоставляет доступ к "пробелу типа", который содержит 100 стертых типов, как показано в следующем коде с помощью синтаксиса подписи F# и пропускания сведений для всех, кроме Type1. Дополнительные сведения о стертых типах см. в разделе "Сведения о стертых предоставленных типах " далее в этом разделе.

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

Обратите внимание, что предоставленный набор типов и элементов является статически известным. В этом примере не используется возможность поставщиков предоставлять типы, зависящие от схемы. Реализация поставщика типов описана в следующем коде, и подробные сведения рассматриваются в последующих разделах этого раздела.

Предупреждение

Между этим кодом и онлайн-примерами могут быть различия.

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 пространстве имен, созданном поставщиком типов.

Перед повторной компиляцией поставщика убедитесь, что вы закрыли все экземпляры Visual Studio и F# Interactive, использующие библиотеку DLL поставщика. В противном случае возникнет ошибка сборки, так как выходные библиотеки DLL будут заблокированы.

Чтобы выполнить отладку этого поставщика с помощью инструкций печати, сделайте скрипт, который предоставляет проблему с поставщиком, а затем используйте следующий код:

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 процессу, в котором вы редактируйте скрипт. С помощью этого метода можно более легко нацеливаться на конкретную логику в поставщике типов, интерактивно вводя выражения во второй экземпляр (с полными функциями IntelliSense и другими функциями).

Вы можете отключить отладку "Только мой код", чтобы лучше определить ошибки в созданном коде. Сведения о включении или отключении этой функции см. в разделе "Навигация по коду" с помощью отладчика. Кроме того, можно также задать исключение первого шанса, открыв Debug меню, а затем выбрав Exceptions или нажав клавиши CTRL+ALT+E, чтобы открыть диалоговое Exceptions окно. В этом диалоговом окне в разделе Common Language Runtime Exceptionsвыберите Thrown поле проверка.

Реализация поставщика типов

В этом разделе описаны основные разделы реализации поставщика типов. Сначала вы определяете тип для самого поставщика настраиваемых типов:

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

Этот тип должен быть общедоступным, и его необходимо пометить атрибутом TypeProvider , чтобы компилятор распознал поставщика типов, когда отдельный проект F# ссылается на сборку, содержащую тип. Параметр конфигурации является необязательным, и, если он присутствует, содержит контекстные сведения о конфигурации для экземпляра поставщика типов, который создает компилятор F#.

Затем вы реализуете интерфейс ITypeProvider . В этом случае тип из API используется TypeProviderForNamespaces в ProvidedTypes качестве базового типа. Этот вспомогательный тип может предоставить ограниченную коллекцию пространств имен, каждый из которых напрямую содержит ограниченное число фиксированных, с нетерпением предоставленных типов. В этом контексте поставщик с нетерпением создает типы, даже если они не нужны или не используются.

inherit TypeProviderForNamespaces(config)

Затем определите локальные частные значения, которые указывают пространство имен для указанных типов и найдите сборку поставщика типов. Эта сборка используется позже в качестве логического родительского типа удаляемых типов.

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

Затем создайте функцию для предоставления каждого типа Type1... Тип100. Эта функция подробно описана далее в этом разделе.

let makeOneProvidedType (n:int) = …

Затем создайте 100 предоставленных типов:

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

Затем добавьте типы в качестве предоставленного пространства имен:

do this.AddNamespace(namespaceName, types)

Наконец, добавьте атрибут сборки, указывающий на создание библиотеки DLL поставщика типов:

[<assembly:TypeProviderAssembly>]
do()

Предоставление одного типа и его членов

Функция 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")

Теперь подключите предоставленное свойство к указанному типу. Необходимо присоединить предоставленный член к одному и только одному типу. В противном случае элемент никогда не будет доступен.

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

Создайте второй предоставленный конструктор, который принимает один параметр:

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

Для InvokeCode конструктора снова возвращается кавычка F#, представляющая код, созданный компилятором узла для вызова метода. Например, можно использовать следующий конструктор:

new Type10("ten")

Экземпляр предоставленного типа создается с базовыми данными "десять". Возможно, вы уже заметили, что InvokeCode функция возвращает кавычки. Входные данные этой функции — это список выражений, один параметр конструктора. В этом случае выражение, представляющее значение одного параметра, доступно в args[0]. Код для вызова конструктора принудивает возвращаемое значение к стертого типа obj. После добавления второго предоставленного конструктора в тип создается предоставленное свойство экземпляра:

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 функция возвращает кавычки. Компилятор узла вызывает эту функцию со списком аргументов. В этом случае аргументы включают только одно выражение, представляющее экземпляр, на котором вызывается метод получения, который можно получить с помощью args[0]. Реализация затем сложений GetterCode в результирующий кавычки в стертый типobj, а приведение используется для удовлетворения механизма компилятора для проверка типов, которые объект является строкой. Следующая часть предоставляет метод экземпляра makeOneProvidedType с одним параметром.

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 в скомпилированном коде. На самом деле базовые объекты в этих примерах являются строками, но тип будет отображаться как System.Object в скомпилированном коде .NET. Как и во всех вариантах использования стирания типов, вы можете использовать явный бокс, распаковку и приведение для подключения стертых типов. В этом случае исключение приведения, которое не является допустимым, может привести к использованию объекта. Среда выполнения поставщика может определить собственный частный тип представления, чтобы защититься от ложных представлений. Не удается определить стертые типы в самом F#. Могут быть удалены только указанные типы. Необходимо понимать, какие последствия используются как практические, так и семантические, при использовании стертых типов для поставщика типов или поставщика, предоставляющего стертые типы. У стертого типа нет реального типа .NET. Таким образом, вы не можете точно отражать тип, и при использовании приведения среды выполнения и других методов, использующих точную семантику типа среды выполнения. Подверсия стертых типов часто приводит к возникновению исключений приведения типов во время выполнения.

Выбор представлений для стертых предоставленных типов

Для некоторых использования стертых указанных типов представление не требуется. Например, стертый тип может содержать только статические свойства и элементы, а конструкторы и никакие методы или свойства не возвращают экземпляр типа. Если вы можете получить доступ к экземплярам стертого типа, необходимо рассмотреть следующие вопросы:

Что такое стирание предоставленного типа?

  • Стирание предоставленного типа заключается в том, как тип отображается в скомпилированном коде .NET.

  • Стирание заданного типа класса всегда является первым нестертым базовым типом в цепочке наследования типа.

  • Эрастира предоставленного стертого типа интерфейса всегда System.Object.

Каковы представления предоставленного типа?

  • Набор возможных объектов для стертого типа называется его представлениями. В этом документе представления всех стертых типов Type1..Type100 всегда являются строковыми объектами.

Все представления предоставленного типа должны быть совместимы с стиранием предоставленного типа. (В противном случае компилятор F# даст ошибку для использования поставщика типов или неподверждаемого кода .NET, который недействителен, будет создан. Поставщик типов недействителен, если он возвращает код, предоставляющий представление, которое не является допустимым.)

Вы можете выбрать представление для предоставленных объектов с помощью любого из следующих подходов, оба из которых очень распространены:

  • Если вы просто предоставляете строго типизированный оболочку по существующему типу .NET, часто имеет смысл удалить тип в этот тип, использовать экземпляры этого типа в качестве представлений или обоих. Этот подход подходит, если большинство существующих методов этого типа по-прежнему имеет смысл при использовании строго типизированной версии.

  • Если вы хотите создать API, который значительно отличается от любого существующего API .NET, рекомендуется создать типы среды выполнения, которые будут стирать и представления типов для предоставленных типов.

В примере в этом документе строки используются в качестве представлений предоставленных объектов. Часто можно использовать другие объекты для представлений. Например, словарь можно использовать в качестве контейнера свойств:

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

В качестве альтернативы можно определить тип в поставщике типов, который будет использоваться во время выполнения для формирования представления, а также одной или нескольких операций среды выполнения:

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

Предоставленные члены могут затем создавать экземпляры этого типа объекта:

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

В этом случае можно (необязательно) использовать этот тип в качестве стирания типа, указав этот тип как при baseType создании ProvidedTypeDefinition:

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

Основные уроки

В предыдущем разделе описано, как создать простой поставщик типов стирания, предоставляющий диапазон типов, свойств и методов. В этом разделе также описана концепция стирания типов, включая некоторые из преимуществ и недостатков предоставления стертых типов от поставщика типов, а также обсуждалось представление стертых типов.

Поставщик типов, использующий статические параметры

Возможность параметризации поставщиков типов статическими данными позволяет выполнять множество интересных сценариев, даже если поставщику не нужно обращаться к локальным или удаленным данным. В этом разделе описаны некоторые основные методы объединения таких поставщиков.

Проверенный поставщик regex

Представьте, что вы хотите реализовать поставщик типов для регулярных выражений, которые упаковывают библиотеки .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 ()

Обратите внимание на следующие аспекты:

  • Поставщик типов принимает два статических параметра: patternобязательный и optionsнеобязательный (так как указано значение по умолчанию).

  • После отправки статических аргументов создается экземпляр регулярного выражения. Этот экземпляр создает исключение, если regex неправильно сформирован, и эта ошибка будет сообщаться пользователям.

  • В обратном вызове DefineStaticParameters вы определяете тип, который будет возвращен после того, как аргументы будут предоставлены.

  • Этот код задает HideObjectMethods значение true, чтобы интерфейс IntelliSense оставался упрощенным. Этот атрибут приводит EqualsGetHashCodeFinalizeк подавлению элементов и GetType элементов из списков IntelliSense для предоставленного объекта.

  • Вы используете 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

Предыдущий код определяет метод IsMatch, который принимает строку в качестве входных данных и возвращает значение bool. Единственной сложной частью является использование аргумента args в определении InvokeCode . В этом примере args представлен список кавычек, представляющих аргументы этого метода. Если метод является методом экземпляра, первый аргумент представляет this аргумент. Однако для статического метода аргументы являются только явными аргументами метода. Обратите внимание, что тип кавычки должен соответствовать указанному типу возвращаемого значения (в данном случае bool). Кроме того, обратите внимание, что этот код использует метод, чтобы убедиться, что предоставленный AddXmlDoc метод также содержит полезную документацию, которую можно предоставить через IntelliSense.

Затем добавьте метод Match экземпляра. Однако этот метод должен возвращать значение предоставленного Match типа, чтобы группы могли быть доступны строго типизированным образом. Таким образом, сначала объявите Match тип. Так как этот тип зависит от шаблона, предоставленного в качестве статического аргумента, этот тип должен быть вложен в определение параметризованного типа:

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

ty.AddMember matchTy

Затем вы добавите одно свойство в тип Match для каждой группы. Во время выполнения совпадение представляется как 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-документацию в предоставленное свойство. Кроме того, обратите внимание, что свойство можно считывать, если указана функция, и свойство можно записать, если GetterCodeSetterCode указана функция, поэтому результирующее свойство доступно только для чтения.

Теперь можно создать метод экземпляра, возвращающий значение этого 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

Конструктор просто удаляется к созданию стандартного экземпляра regex .NET, который снова упакован в объект, так как 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

В этом разделе показано, как указать тип, который можно использовать для получения строк со свойством Distance типа float<meter> и Time свойства типа float<second>. Для простоты выполняются следующие предположения:

  • Имена заголовков меньше единицы или имеют форму "Имя (единица)" и не содержат запятые.

  • Единицы являются единицами system International (SI), так как определяет модуль FSharp.Data.UnitSystems.SI.UnitNames (F#).

  • Единицы являются простыми (например, счетчиками), а не составной (например, счетчиком и секундой).

  • Все столбцы содержат данные с плавающей запятой.

Более полный поставщик ослабит эти ограничения.

Еще раз первым шагом является рассмотрение того, как должен выглядеть 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>, а второй — 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 в предоставленном свойстве, CSV-файл откроется в Visual Studio.

  • Тип можно использовать 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 свойство были удалены, оставляя только операции с используемыми типами среды выполнения.

Соглашения о проектировании и именовании для поставщиков типов

При создании поставщиков типов соблюдайте следующие соглашения.

Поставщики протоколов Подключение ivity, как правило, имена библиотек DLL большинства поставщиков для протоколов подключения к данным и службам, например OData или SQL, должны заканчиваться 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+">()

Источники данных singleton. Некоторые поставщики типов подключаются к одному выделенному источнику данных и предоставляют только данные. В этом случае следует удалить TypeProvider суффикс и использовать обычные соглашения для именования .NET:

#r "Fabrikam.Data.Freebase.dll"

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

Дополнительные сведения см. в соглашении GetConnection о проектировании, описанном далее в этом разделе.

Шаблоны проектирования для поставщиков типов

В следующих разделах описаны шаблоны проектирования, которые можно использовать при создании поставщиков типов.

Шаблон конструктора Get Подключение ion

Большинство поставщиков типов должны быть записаны для использования GetConnection шаблона, используемого поставщиками типов в FSharp.Data.TypeProviders.dll, как показано в следующем примере:

#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

  • учетные данные и проверка подлинности

В этом разделе не рассматриваются эти проблемы дальше.

Дополнительные методы разработки

При написании собственных поставщиков типов может потребоваться использовать следующие дополнительные методы.

Создание типов и элементов по запросу

API ProvidedType задерживает версии AddMember.

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

Эти версии используются для создания пространств типов по запросу.

Предоставление типов массивов и экземпляров универсальных типов

Вы делаете предоставленные члены (сигнатуры которых включают типы массивов, типы byref и экземпляры универсальных типов) с помощью обычногоMakeArrayType, а также MakeGenericType для любого экземпляраType, включаяProvidedTypeDefinitionsMakePointerType.

Примечание.

В некоторых случаях может потребоваться использовать вспомогательный элемент ProvidedTypeBuilder.MakeGenericType. Дополнительные сведения см. в документации по пакету SDK поставщика типов.

Предоставление заметок единиц измерения

API ProvidedTypes предоставляет вспомогательные средства для предоставления заметок мер. Например, чтобы указать тип 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 |]

Доступ к локальным или локальным ресурсам проекта

Каждый экземпляр поставщика типов может быть присвоено TypeProviderConfig значение во время построения. Это значение содержит папку разрешения для поставщика (то есть папку проекта для компиляции или каталога, содержащего скрипт), список ссылок на сборки и другие сведения.

Недействительность

Поставщики могут вызывать сигналы о недопустимости, чтобы уведомить языковую службу F# о том, что предположения схемы могли измениться. При возникновении недопустимости тип проверка будет переопределен, если поставщик размещается в Visual Studio. Этот сигнал будет игнорироваться, если поставщик размещается в F# Interactive или компилятором F# (fsc.exe).

Кэширование сведений о схеме

Поставщики часто кэшируют доступ к сведениям схемы. Кэшированные данные должны храниться с помощью имени файла, заданного как статический параметр или как пользовательские данные. Пример кэширования схемы — параметр LocalSchemaFile в поставщиках типов в сборке FSharp.Data.TypeProviders . В реализации этих поставщиков этот статический параметр направляет поставщика типов использовать сведения о схеме в указанном локальном файле вместо доступа к источнику данных через сеть. Чтобы использовать кэшированные сведения о схеме, необходимо также задать для falseстатического параметра ForceUpdate значение . Вы можете использовать аналогичный метод для включения доступа к данным в сети и автономном режиме.

Резервная сборка

При компиляции или .exe файла резервный .dll файл .dll для созданных типов статически связан с результирующей сборкой. Эта ссылка создается путем копирования определений типов промежуточного языка (IL) и всех управляемых ресурсов из резервной сборки в окончательную сборку. При использовании F# Interactive резервный файл .dll не копируется и загружается непосредственно в интерактивный процесс F#.

Исключения и диагностика от поставщиков типов

Все использование всех элементов из предоставленных типов может вызывать исключения. Во всех случаях, если поставщик типов выдает исключение, компилятор узла атрибутирует ошибку конкретному поставщику типов.

  • Исключения поставщика типов никогда не должны привести к внутренним ошибкам компилятора.

  • Поставщики типов не могут сообщать предупреждения.

  • Если поставщик типов размещается в компиляторе F#, среде разработки F# или F# Interactive, все исключения из этого поставщика перехватываются. Свойство Message всегда является текстом ошибки, и трассировка стека не отображается. Если вы собираетесь вызвать исключение, можно вызвать следующие примеры: System.NotSupportedException, System.IO.IOException. System.Exception

Предоставление созданных типов

До сих пор в этом документе объясняется, как предоставлять стертые типы. Вы также можете использовать механизм поставщика типов в F# для предоставления созданных типов, которые добавляются как реальные определения типов .NET в программу пользователей. Необходимо ссылаться на созданные типы с помощью определения типа.

open Microsoft.FSharp.TypeProviders

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

Вспомогательный код ProvidedTypes-0.2, который является частью выпуска F# 3.0, имеет только ограниченную поддержку предоставления созданных типов. Следующие инструкции должны иметь значение true для определения созданного типа:

  • Для параметра isErased нужно задать значение false.

  • Созданный тип необходимо добавить в только что созданное ProvidedAssembly(), которое представляет контейнер для созданных фрагментов кода.

  • Поставщик должен иметь сборку с фактическим резервным файлом .NET .dll с соответствующим .dll файлом на диске.

Правила и ограничения

При написании поставщиков типов следует учитывать следующие правила и ограничения.

Предоставленные типы должны быть доступны

Все указанные типы должны быть доступны из не вложенных типов. Не вложенные типы задаются в вызове конструктора TypeProviderForNamespaces или вызова AddNamespace. Например, если поставщик предоставляет тип StaticClass.P : T, необходимо убедиться, что T является типом, не вложенным или вложенным в один.

Например, у некоторых поставщиков есть статический класс, например DataTypes содержащий эти T1, T2, T3, ... типы. В противном случае ошибка означает, что найдена ссылка на тип T в сборке A, но тип не найден в этой сборке. Если появится эта ошибка, убедитесь, что все подтипы можно получить из типов поставщиков. Примечание. Эти T1, T2, T3... типы называются типами на лету . Не забудьте поместить их в доступное пространство имен или родительский тип.

Ограничения механизма поставщика типов

Механизм поставщика типов в F# имеет следующие ограничения:

  • Базовая инфраструктура для поставщиков типов в F# не поддерживает предоставленные универсальные типы или предоставленные универсальные методы.

  • Механизм не поддерживает вложенные типы со статическими параметрами.

Советы по разработке

Вы можете найти следующие советы, полезные во время процесса разработки:

Запуск двух экземпляров Visual Studio

Вы можете разработать поставщик типов в одном экземпляре и протестировать поставщика в другом, так как тестовая интегрированная среда разработки будет блокировать файл .dll, который предотвращает перестроение поставщика типов. Таким образом, необходимо закрыть второй экземпляр Visual Studio во время сборки поставщика, а затем повторно открыть второй экземпляр после создания поставщика.

Поставщики типов отладки с помощью вызовов fsc.exe

Вы можете вызвать поставщиков типов с помощью следующих средств:

  • fsc.exe (компилятор командной строки F#)

  • fsi.exe (интерактивный компилятор F#)

  • devenv.exe (Visual Studio)

Поставщики типов часто отладить можно с помощью fsc.exe в файле тестового скрипта (например, script.fsx). Отладчик можно запустить из командной строки.

devenv /debugexe fsc.exe script.fsx

Ведение журнала печати в stdout можно использовать.

См. также