Учебное руководство. Создание поставщика типов (F#)
Механизм поставщиков типов в языке F# 3.0 — важная часть его поддержки для информационно-насыщенного программирования. В этом руководстве рассматривается создание собственных поставщиков типа путем разработки нескольких простых поставщиков типа для иллюстрации основных понятий. Дополнительные сведения о механизме поставщика типов в F# см. в разделе Поставщики типов.
F# 3.0 содержит несколько встроенных поставщиков типа для часто используемых веб-служб и служб данных предприятия. Эти поставщики типа предоставляют простой обычный доступ к реляционным базам данных SQL и к сетевым службам OData и WSDL. Эти поставщики также поддерживают использование запросов LINQ F# для этих источников данных.
При необходимости можно создать пользовательские поставщики типа или можно использовать уже созданные поставщики типа. Например, ваша организация может иметь службу данных, который поставляет большие и постоянно растущие наборы данных, каждый из которых имеет свою стабильную схему данных. Можно создать поставщик типа, который считывает схемы и представляет текущие наборы данных программисту в строго типизированном виде.
Перед началом установки
Механизм поставщиков типа, в первую очередь, предназначен для введения стабильных данных и служебного информационного пространства в опыт программирования на языке F#.
Этот механизм не предназначен для введения информационного пространства, чья схема меняется во время выполнения программы способами, относящимися к логике программы. Кроме того, механизм не предназначен для внутриязыкового мета-программирования, даже если эта сфера содержит некоторые допустимые использования. Этот механизм следует использовать только при необходимости, когда разработка поставщика типа имеет большую значимость.
Следует избегать написания поставщика типа, когда схема недоступна. Кроме того, следует избегать написания поставщика типа, где обычной (или даже уже существующей ) библиотеки .NET было бы достаточно.
Прежде чем начать, можно подумать об ответах на следующие вопросы:
Имеется ли схема для источника данных? Если это так, каково сопоставление систем типов F# и .NET?
Можно ли использовать существующее (динамически типизированное) API в качестве отправной точки для реализации?
Будет ли поставщик типа использоваться настолько часто, что его написание имеет смысл? Могла бы соответствовать поставленным требованиям обычная библиотека .NET ?
На сколько нужно будет изменить схему?
Будет ли она изменятся во время кодирования?
Будит ли она меняться между этапами кодирования?
Будет ли она меняться во время выполнения программы?
Поставщики типа наиболее подходят для ситуаций, где схема не меняется во время выполнения и во время жизненного цикла скомпилированного кода.
Простой поставщик типа
В этом примере Samples.HelloWorldTypeProvider в каталоге SampleProviders\ProvidersПакет образца F# 3.0 веб-сайта Codeplex. Поставщик делает доступным «пространство типа», которая содержит 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
/// This is an instance property.
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 Samples.FSharp.ProvidedTypes
open Microsoft.FSharp.Core.CompilerServices
open Microsoft.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()
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 2012, создайте скрипт 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, откройте меню Отладки, выберите Отладка/Присоединиться к процессу и присоединитесь к другому devenv процессу, в котором открыто редактирование скрипта. С помощью этого метода можно легче достигнуть отдельной логики в поставщике типа путем интерактивного ввода выражений во второй экземпляр(используя полную поддержку IntelliSense и другие особенности) .
Можно отключить режим "Только мой код" при отладке для лучшего поиска ошибок в сгенерированном коде. Дополнительные сведения о том, как включить или отключить эту функцию, см. в разделе Ограничить установки скорости для в режим " Только мой код ". Кроме того, можно также задать свойство ловли первичного исключения, открыв меню Отладка а затем выбрать Исключения, либо путем нажатия Ctrl+Alt+E, чтобы открыть диалоговое окно Исключения. В этом диалоговом окне, в Исключения среды CLR установите флажок Вызванное.
Реализация поставщика типа
В этом разделе описываются основные части реализации поставщика типа. Во-первых, указывается тип для самого пользовательского типа поставщика.
[<TypeProvider>]
type SampleTypeProvider(config: TypeProviderConfig) as this =
Этот тип должен быть открытым, и необходимо отметить его атрибутом TypeProvider, чтобы компилятор распознал поставщик типа, если отдельный проект на языке F# будет ссылаться на сборку, содержащую это тип. Параметр config является необязательным и, если он имеется, содержит контекстуальную информацию о конфигурации для экземпляра поставщика типа, созданного компилятором F#.
Далее необходимо реализовать интерфейс ITypeProvider. В этом случае следует использовать тип TypeProviderForNamespaces из API-интерфейса ProvidedTypes в качестве базового типа. Этот вспомогательный тип может поставлять конечную коллекцию предупредительно поставляемых пространств имен, каждый из которых содержит конечное множество рабочих, предупредительно поставленных типов. В этом контексте, поставщик предупредительно создает типы, даже если они не требуются или не используются.
inherit TypeProviderForNamespaces()
Затем укажите локальные частные значения, определяющие пространство имен для поставляемых типов и найдите саму сборку поставщика типа Эта сборка используется далее в качестве логического родительского типа поставляемых стертых типов.
let namespaceName = "Samples.HelloWorldTypeProvider"
let thisAssembly = Assembly.GetExecutingAssembly()
Теперь создайте функцию для поставки каждого из типов Type1… Type100. Об этой функции будет рассказано подробнее далее в этом разделе.
let makeOneProvidedType (n:int) = …
Затем создайте 100 поставленных типов:
let types = [ for i in 1 .. 100 -> makeOneProvidedType i ]
Далее добавьте типы, предоставленные пространством имен:
do this.AddNamespace(namespaceName, types)
Наконец, добавьте атрибут сборки, означающий, что создается библиотека DLL поставщика типа:
[<assembly:TypeProviderAssembly>]
do()
Поставка одного типа и его элементов
Функция 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 () -> sprintf "This provided type %s" ("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()
Экземпляр поставляемого типа создается с исходными данными "The object data". Цитированный код включает преобразование в obj, поскольку этот тип — cтирание поставляемого типа, как было указано при его объявлении.
Добавьте 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")
Экземпляр поставщика типа создается с исходным значением «10». Можно заметить, что функция 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 do
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 () ->
sprintf "This is StaticProperty%d on NestedType" i)
yield p ]
staticPropsInNestedType)
[nestedType])
// The result of makeOneProvidedType is the type.
t
Сведения о стертых поставляемых типах.
Пример в этом разделе описывает только стертые поставляемые типы, которые особенно полезны в следующих случаях:
При написании поставщика для информации, которая содержит только данные и методы.
При написании поставщика, у которого точная семантика времени выполнения не является критической для практического использования информационного пространства.
При написании поставщика информационного пространства, которое настолько велико и взаимосвязано, что технически невозможно сформировать типы .NET информационного пространства.
В этом примере каждый поставляемый тип стерт до типа obj, и все применения типа отображаются как тип obj в скомпилированном коде. В действительности базовые объекты в этих примерах — строки, но их тип отображается Object в компилированном коде .NET. Как и для всех типов стирания, можно использовать явную запаковку, распаковку и приведение к разрушенному стертому типу. В этом случае может возникнуть исключение приведения, которое может возникнуть, если объект используется. Среда поставщика выполнения может предоставить свой закрытый тип представления, помогающий против неверного представления. Нельзя определить стертые типы в языке F#. Только поставляемые типы могут быть стертыми. Необходимо понять практические и семантические последствия использования стертых типов для поставщика типа или поставщика, который предоставляет стертые типы. Стертый тип не является действительным типом .NET. Следовательно, нельзя сделать точное отображение через тип, и можно разрушить стертые типы при использовании приведения среды выполнения и других методов, зависящие от явной семантики среды выполнения. Диверсия стертых типов часто приводит к исключению приведения типа во время выполнения.
Выбор представления для поставленных стертых типов
При некотором использовании поставленных стертых типов никакое представление не нужно. Например, поставленный стертый тип может содержать только статические свойства и элементы, и нет таких конструкторов, методов или свойств, которые бы возвращали экземпляр типа. Если есть возможность получить экземпляр стертого типа, необходимо рассмотреть следующие вопросы:
Что есть стирание поставленного типа?
Стирание поставленного типа в качестве типа отображается в скомпилированный коде .NET.
Стирание поставленного стертого типа класса всегда наследуется от не стертого базового тип в цепи наследования типов.
Интерфейс стирания поставленного стертого типа всегда 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() @@>), …)
Ключевые занятия
В предыдущем разделе объяснялось, как создать простой стертый поставщик типа, который предоставляет диапазон типов, свойств и методов. В разделе также объяснялось понятие стирания типа, включая некоторые из преимуществ и недостатков поставки стертых типов из поставщика типов, и обсуждались представления стертых типов.
Поставщик типа, который использует статические параметры
Возможность параметризовать поставщики типа статическим данными используется во многих интересных сценариях, даже в тех случаях, когда поставщик не требует получения доступа к любым локальным или удаленным данным. В этом разделе описаны некоторые основные методы для таких поставщиков.
Поставщик типа для проверяемых регулярных выражений
Предположим, что нужно реализовать поставщика типов для регулярных выражений, который создает программу-оболочку библиотеки .NET Regex в интерфейсе, который предоставляет следующие гарантии времени компиляции:
Проверка, является ли регулярное выражение допустимым.
Предоставляет именованные свойства соответствий, которые основаны на именах всех групп в регулярном выражении.
В данном разделе, показывается, как использовать поставщики типа для создания типа шаблона регулярного выражения RegExProviderType, который выполняет параметризацию, чтобы предоставить указанные преимущества. Компилятор сообщит ошибку, если указанный шаблон является недопустимым, а поставщик типа может извлечь группы из шаблона, чтобы можно было получить доступ к ним с помощью именованных свойств соответствия. При проектировании поставщика типа необходимо учитывать, как должно выглядеть предоставленное 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"
Обратите внимание на следующие моменты.
Стандартный тип регулярного выражения представляет параметризованный тип RegexTyped.
Конструктора RegexTyped возвращает результаты при вызове конструктора регулярного выражения, передавая статический аргумент типа шаблона.
Результаты метода Match представлены стандартным типом Match.
Каждая группа с именем выливается в указанное свойство, и получение доступа к свойству приводит к использованию индексатора в коллекции Groups совпадения.
В следующем коде реализовано ядро логики для такого поставщика, в примере не приводится добавление всех членов в поставляемый тип. Дополнительные сведения о каждом добавленном участнике см. в соответствующем разделе далее. Полный образец кода можно загрузить из Пакет образца F# 3.0 веб-сайта Codeplex.
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, которое является необязательным, поскольку указано его значение по умолчанию.
После того, как передаются статические аргументы, создается экземпляр регулярного выражения. Этот экземпляр создает исключение, если регулярное выражение неправильно, и эта ошибка будет сообщена пользователю.
В объекте обратного вызова 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>,
IsStaticMethod = 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(sprintf @"Gets the ""%s"" group from this match" group)
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
Конструктор просто стирает к созданию стандартного экземпляра класса 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>,
IsStaticMethod = 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 occurence 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>. Для простоты предполагается следующее:
Имена заголовков или не содержат единиц измерения или имеют форму «имя (единица)» и не содержат запятые.
Единицы измерения — международные (СИ), определенные в модуле Microsoft.FSharp.Data.UnitSystems.SI.UnitNames Module (F#).
Единицы являются простыми (например, метр), а не смешанными (например, метр/секунда).
Все столбцы содержат данные с плавающей запятой.
Более сложный и полный поставщик мог бы опустить эти ограничения.
На первом шаг нужно рассмотреть, как должно выглядеть API. Файл данных info.csv с содержимым из предыдущей таблицы (в виде отделенном формате поставщика), пользователи должны иметь возможность писать код, который будет выглядеть примерно так:
let info = new MiniCsv<"info.csv">()
for row in info.Data do
let time = row.Time
printfn "%f" (float time)
В этом случае компилятор должен преобразовать эти вызовы в что-то, как в следующем примере:
let info = new MiniCsvFile("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 do
yield line.Split(',') |> Array.map float }
|> Seq.cache
member __.Data = data
[<TypeProvider>]
type public MiniCsvProvider(cfg:TypeProviderConfig) as this =
inherit TypeProviderForNamespaces()
// 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 runtime.
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, чтобы определить расположение, поставляемых свойств. Поэтому при использовании Перейти к определению на указанном свойстве CSV-файл будет открыт в Visual Studio.
Можно использовать тип ProvidedMeasureBuilder для поиска единиц СИ и создавать необходимые типы 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+">()
Источники данных, существующие в единственном экземпляре
Некоторые поставщики типа подключены к одному выделенному источнику данных и предоставляют только данные. В этом случае необходимо удалить суффикс 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
Эти версии используются для создания пространства типов по запросу.
Поставка массивов, типов, передаваемых по ссылке, и указателей
Можно поставлять члены, сигнатуры которых содержат массивы, передачу по ссылке и универсальные типы, с помощью обычного MakeArrayType, MakePointerType и MakeGenericType на любом экземпляре System.Type, включая ProvidedTypeDefinitions.
Поставка аннотаций единиц измерения
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# или компиляторе F# (fsc.exe).
Сведения о схеме кэширования
Поставщики обычно должны кэшировать доступ к сведениям о схеме. Кэшированные данные должны храниться с помощью имени файла, которое задается в качестве статического параметра, или как данные пользователя. Пример кэширования схем — параметр LocalSchemaFile в поставщиках типа в сборке FSharp.Data.TypeProviders. В реализации этих поставщиков данный статический параметр указывает, что поставщик типа использует сведения схемы в указанном локальном файле вместо обращения к источнику данных по сети. Для использования сведений о схеме необходимо также установить статическй параметр ForceUpdate в false. Можно использовать подобный метод, чтобы включить подключенный и автономный доступ к данным.
Поддерживающая сборка
При компилировании DLL-файла или EXE-файла, поддерживающий DLL-файл для создаваемых типов статически связывается в результирующую сборку. Эта связь создется путем копирования определений типов на промежуточном языке (IL) и всех управляемых ресурсов из поддерживающей сборки в окончательную сборку. При использовании F# Interactive, поддерживающий DLL-файл не копируется и загружается непосредственно в процесс F# Interactive.
Исключения из поставщиков типа и устранение неполадок
Любые использования любых членов из поставляемых типов могут генерировать исключения. Если поставщик типа вызывает исключение, то компилятор во всех случаях приписывает ошибку к определенному поставщику типа.
Исключения поставщика типов никогда не должны приводить к возникновению внутренних ошибках компилятора.
Поставщики типа не могут сообщать предупреждения.
Когда поставщик типа запущен в компиляторе F#, интегрированн в среде разработки F# или в F# Interactive, все исключения из этого поставщика перехватываются. Свойство Message всегда содержит текст ошибки и трассировка стека не появляется. Если планируется создавать исключение, можно создать следующие примеры:
Поставка универсальных типов
До сих пор, этот документ объяснял, как поставляеть стертые типы. Можно также использовать механизм поставщика типов в языке F# для предоставления созданных типов, которые добавляются как фактические определения типов .NET в программы пользователей поставщика. Необходимо обратиться к созданным предоставленным типам с помощью определения типа.
open Microsoft.FSharp.TypeProviders
type Service = ODataService<" http://services.odata.org/Northwind/Northwind.svc/">
Вспомогательный код ProvidedTypes-0.2, который является частью выпуска F# 3.0, имел ограниченнуюю поддержку для предоставления созданных типов. Следующие утверждения должны быть истины созданного определения типа:
IsErased должно быть установлено в false.
Поставщик должен иметь сборку, которая имеет фактический поддерживающий DLL-файл на диске.
Кроме того, необходимо вызвать ConvertToGenerated в корневом поставляемом типе, чьи вложенные типы формируют закрытый набор создаваемых типов. Этот вызов выпускает заданное определение поставляемого типа и определения его вложенных типов в сборку и корретктирует свойство Assembly всех определений поставляемых типов для возрата этой сборки. Сборка создается только когда к свойству Assembly в корневом типе осуществляется доступ первый раз. Компилятор F# обращается к этому свойству, когда он обрабатывает порожадающее объявление типа для типа.
Правила и ограничения
При написании поставщика типа нужно иметь в виду следующие правила и ограничения.
Поставляемые типы должны быть доступны.
Все поставляемые типы должны быть доступны из типов, не являющихся вложенными . Невложенные типы получены при вызове конструктора TypeProviderForNamespaces или вызове AddNamespace. Например, если поставщик предоставляет тип StaticClass.P : T, необходимо убедиться в том, что T или невложенный тип или вложенный под одним.
Например, некоторые поставщики имеют такой статический класс, как DataTypes, в состав которых входят эти типы T1, T2, T3, ... В противном случае ошибка указывает на то, что найдена ссылка на тип T в сборке A, но тип не найден в данной сборке. Если эта ошибка появляется, то убедитесь в том, что все подтипы могут быть достигнуты из типов поставщика. Примечание. Эти типы T1, T2, T3... называются типами времени выполнения. Не забудьте поместить их в доступное пространство имен или родительский тип.
Ограничения механизма поставщика типа
Механизм поставщиков типов в языке F# имееет следующие ограничения:
Стандартная инфраструктура для поставщиков типов в языке F# не поддерживает поставку универсальных типов или универсальных методов.
Механизм не поддерживает вложенные типы со статическими параметрами.
Ограничения кода поддержки ProvidedTypes
Код поддержки ProvidedTypes имеет следующие правила и ограничения.
Не реализованы поставляемые свойства с индексированными геттерами и сеттерами.
Не поддерживаются поставляемые события.
Поставляемые типы и объекты сведений должны использоваться только для механизма поставщиков типов в языке F#. В общем случае они не могут использоваться в качестве объектов System.Type.
Конструкции, которые можно использовать в цитатах для определения реализации метода, имеют некоторые ограничения. Обращаться к исходному коду для ProvidedTypes-Version, чтобы узнать, какие конструкции поддерживаются в кавычках.
Поставщики типа должны создавать сборки, выходные файлы DLL, не exe-файлов.
Советы по разработке
Можно найти следующие советы, которые могут оказаться полезными в процессе разработки.
Запуск двух экземпляров 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
Можно использовать ведение журнала отладочной печатью.