Примечание
Для доступа к этой странице требуется авторизация. Вы можете попробовать войти или изменить каталоги.
Для доступа к этой странице требуется авторизация. Вы можете попробовать изменить каталоги.
Механизм поставщика типов в 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 . В этом случае вы используете тип TypeProviderForNamespaces
из API 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 оставался упрощенным. Этот атрибут приводит к тому, что элементыEquals
,GetHashCode
,Finalize
и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-документацию в предоставленное свойство. Кроме того, обратите внимание, что свойство можно считывать, если указана функция 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 |
В этом разделе показано, как указать тип, который можно использовать для получения строк со свойством Distance
типа float<meter>
и Time
свойства типа float<second>
. Для простоты выполняются следующие предположения:
Имена заголовков либо не содержат единиц измерения, либо оформлены как "Имя (единица)" и не содержат запятых.
Единицы являются единицами Международной системы (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
свойстве были удалены, оставляя только операции с используемыми типами среды выполнения.
Соглашения о проектировании и именовании для поставщиков типов
При создании типов поставщиков следуйте следующим правилам.
Поставщики протоколов подключения Как правило, имена большинства библиотек 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
о проектировании, описанном далее в этом разделе.
Шаблоны проектирования для поставщиков типов
В следующих разделах описаны шаблоны проектирования, которые можно использовать при создании поставщиков типов.
Шаблон проектирования GetConnection
Большинство поставщиков типов должны быть разработаны для использования шаблона 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
, MakePointerType
и MakeGenericType
на любом экземпляре Type, включая ProvidedTypeDefinitions
.
Замечание
В некоторых случаях может потребоваться использовать вспомогательный элемент 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 |]
Доступ к ресурсам Project-Local или Script-Local
Каждому экземпляру поставщика типов можно задать значение TypeProviderConfig
во время построения. Это значение содержит папку разрешения для поставщика (то есть папку проекта для компиляции или каталога, содержащего скрипт), список ссылок на сборки и другие сведения.
Недействительность
Поставщики могут вызывать сигналы об аннулировании, чтобы уведомить языковую службу F#, что предположения относительно схемы могли измениться. При аннулировании повторно выполняется проверка типа, если поставщик размещается в Visual Studio. Этот сигнал будет игнорироваться при размещении поставщика в F# Interactive или при использовании компилятора F# (fsc.exe).
Кэширование сведений о схеме
Поставщики часто кэшируют доступ к сведениям схемы. Кэшированные данные должны храниться с помощью имени файла, заданного как статический параметр или как пользовательские данные. Пример кэширования схемы — параметр LocalSchemaFile
в поставщиках типов в сборке FSharp.Data.TypeProviders
. В реализации этих поставщиков этот статический параметр направляет поставщика типов использовать сведения о схеме в указанном локальном файле вместо доступа к источнику данных через сеть. Чтобы использовать кэшированные сведения о схеме, необходимо также задать значение статического параметра ForceUpdate
в false
. Вы можете использовать аналогичный метод для обеспечения доступа к данным в сети и автономном режиме.
Опорная сборка
При компиляции .dll
или .exe
файла резервный .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, имеет только ограниченную поддержку предоставления созданных типов. Следующие утверждения должны быть истинными для определения сформированного типа:
Для параметра
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.