자습서: 형식 공급자 만들기(F#)

F# 3.0의 형식 공급자 메커니즘은 정보가 풍부한 프로그래밍의 지원에 중요한 부분입니다.이 자습서는 기본 개념을 설명하기 위해 몇 가지 간단한 형식 공급자를 개발하여 사용자 고유의 형식 공급자를 만드는 방법을 설명 합니다.F#의 형식 공급자 메커니즘에 대한 자세한 내용은 형식 공급자를 참조하십시오.

F# 3.0에는 자주 사용하는 인터넷 및 엔터프라이즈 데이터 서비스에 대한 몇 가지 기본 제공된 형식 공급자가 있습니다.이러한 형식 공급자는 SQL 관계형 데이터베이스 및 네트워크 기반 OData 및 WSDL 서비스에 간단하고 일반적인 액세스를 제공합니다.이러한 공급자는 또한 이러한 데이터 소스에 대한 F# LINQ 쿼리를 사용하기도 합니다.

필요한 경우 사용자 지정 형식 공급자를 만들거나 다른 사람이 만든 형식 공급자를 참조할 수 있습니다.예를 들어, 조직마다 각각 자체적으로 안정적인 데이터 스키마가 있는 크고 점점 많은 명명된 데이터 집합을 제공하는 데이터 서비스를 가질 수 있습니다.강력한 방식으로 스키마를 읽고 프로그래머에 설정된 현재 데이터 집합을 제공하는 형식 공급자를 만들 수 있습니다.

시작하기 전에

형식 공급자 메커니즘은 F# 프로그래밍 경험으로 안정적인 데이터와 서비스 정보 공간을 주입하도록 기본적으로 설계됩니다.

이 메커니즘은 프로그램 논리와 관련된 방식으로 프로그램을 실행하는 동안 스키마가 변경하는 정보 공간을 주입하도록 설계되지 않았습니다.또한, 해당 도메인에 유효한 일부 사용이 포함되어 있는 경우에도 언어 내부의 메타데이터 프로그래밍에 대한 메커니즘이 설계되어 있지 않습니다.형식 공급자의 개발 시 매우 높은 값을 발생시키는 위치에서만 이 메커니즘을 사용해야 합니다.

스키마를 사용할 수 없는 형식 공급자를 작성하면 안 됩니다.마찬가지로 일반(또는 기존) .NET 라이브러리가 충분한 형식 공급자를 작성하는 것을 피해야 합니다.

시작하기 전에 다음 질문을 요청할 수 있습니다.

  • 정보 소스에 대한 스키마가 있습니까?이러한 경우 F# 및 .NET 형식 시스템으로의 매핑 대상은 무엇입니까?

  • 기존 API(동적으로 입력)를 구현에 대한 시작 지점으로 사용할 수 있습니까?

  • 사용자와 조직이 충분한 가치가 있게 작성하도록 형식 공급자를 사용해야 합니까?일반 .NET 라이브러리에서 사용자 요구를 충족합니까?

  • 스키마 변경 사항이 얼마나 됩니까?

  • 코딩 중 변경됩니까?

  • 코딩 세션 사이에 변경됩니까?

  • 프로그램 실행 시 변경됩니까?

형식 공급자는 런타임 시와 컴파일된 코드의 수명 기간 동안 스키마를 안정적으로 유지하는 환경에 가장 적합합니다.

단순 형식 공급자

이 샘플은 CodePlex 웹 사이트에서 F# 3.0 샘플 팩의 SampleProviders\Providers 디렉터리 내에 있는 Samples.HelloWorldTypeProvider입니다.공급자는 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 @".\bin\Debug\Samples.HelloWorldTypeProvider.dll"

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

let obj2 = Samples.HelloWorldTypeProvider.Type1("some other data")

obj1.InstanceProperty
obj2.InstanceProperty

[ for index in 0 .. obj1.InstanceProperty-1 -> obj1.InstanceMethod(index) ]
[ for index in 0 .. obj2.InstanceProperty-1 -> obj2.InstanceMethod(index) ]

let data1 = Samples.HelloWorldTypeProvider.Type1.NestedType.StaticProperty35

Samples.HelloWorldTypeProvider에서 형식 공급자가 생성된 형식을 찾습니다.

공급자를 다시 컴파일하기 전에 공급자 DLL을 사용하고 있는 Visual Studio 및 F# Interactive의 모든 인스턴스를 닫았는지 확인하십시오.그렇지 않은 경우 출력 DLL이 잠기기 때문에 빌드 오류가 발생합니다.

인쇄 문을 사용하여 이 공급자를 디버깅하려면 공급자 문제를 노출하는 스크립트를 만든 후 다음 코드를 사용합니다.

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 키를 선택하여 예외 대화 상자를 열어 첫째 예외 catch를 설정할 수 있습니다.해당 대화 상자의 Common Language Runtime Exceptions에서 Throw됨 확인란을 선택합니다.

Hh361034.collapse_all(ko-kr,VS.110).gif형식 공급자의 구현

이 단원에서는 형식 공급자 구현의 주요 단원을 안내합니다.먼저, 사용자 지정 형식 공급자에 대한 형식을 정의합니다.

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

이 형식은 공용 이어야 하고 TypeProvider 특성을 함께 표시해야 하므로 컴파일러는 별도 F# 프로젝트에서 형식이 포함된 어셈블리를 참조할 때 컴파일러에서 형식 공급자 인식할 수 있도록 합니다.config 매개 변수는 옵션이며 있을 경우 F# 컴파일러가 만드는 형식 공급자 인스턴스에 대한 컨텍스트 구성 정보가 포함됩니다.

다음 단계로, ITypeProvider 인터페이스를 구현합니다.이 경우 ProvidedTypes API에서 TypeProviderForNamespaces 형식을 기본 형식으로 사용합니다.이 도우미 형식은 즉시 제공된 네임스페이스의 유한한 컬렉션을 제공할 수 있으며, 각각 즉시 제공되는 형식의 유한한 수를 포함합니다.이 컨텍스트에서 공급자는 필요하거나 사용되지 않는 경우에도 형식을 즉시 생성합니다.

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

Hh361034.collapse_all(ko-kr,VS.110).gif형식 및 해당 멤버 제공

makeOneProvidedType 함수는 형식 중 하나를 제공하는 실제 작업을 수행합니다.

let makeOneProvidedType (n:int) = 
        …

이 단계는 이 함수의 구현에 대해 설명합니다.먼저 제공된 형식을 만듭니다(예를 들어, n = 1일 때는 Type1 또는 n = 57일 때는 Type57).

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

제공된 형식의 인스턴스는 기본 데이터인 "개체 데이터"로 만들어집니다.따옴표로 묶인 코드는 해당 형식이 제공된 형식의 지우기이므로(제공된 형식을 선언했을 때 지정) 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")

제공된 형식의 인스턴스는 기본 데이터인 "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 함수에서 따옴표를 반환합니다.호스트 컴파일러는 이 인수 목록으로 이 함수를 호출합니다.이 경우 인수는 getter가 호출하는 인스턴스를 나타내는 단일 식만 포함하며, 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

Hh361034.collapse_all(ko-kr,VS.110).gif제공된 형식에 대한 세부 정보

이 단원의 예제는 다음 상황에서 특히 유용한 지워진 공급된 형식만 제공합니다.

  • 데이터와 메서드만 포함하는 정보 공간에 대한 공급자를 작성하는 경우입니다.

  • 정확한 런타임 형식의 의미 체계가 정보 공간에 대한 실제적인 용도로 중요하지 않는 공급자를 작성하는 경우입니다.

  • 너무 크고 상호 연결되어 있어 실제 .NET 형식을 생성하는 것이 정보 공간에서 기술상 불가능한 정보 공간에 대해 공급자를 작성하는 경우입니다.

이 예제에서 제공되는 각 형식은 obj 형식으로 지워지며 모든 형식의 사용은 컴파일된 코드에 형식 obj로 나타납니다.사실 이러한 예의 기본 개체는 문자열이지만 형식은 .NET 컴파일 코드에서 Object로 나타납니다.형식 지우기의 모든 사용과 마찬가지로 명시적인 boxing, unboxing 및 캐스팅을 사용하여 삭제된 형식을 되돌릴 수 있습니다.이 경우 개체를 사용할 때 유효하지 않은 캐스팅 예외가 발생할 수 있습니다.공급자 런타임은 자신의 개인 표현 형식을 정의하여 false로 표현되지 않도록 보호합니다.지워진 형식을 F# 자체에서 정의할 수 없습니다.제공된 형식만 지울 수 있습니다.형식 공급자에 대해 지워진 형식이나 지워진 형식을 제공하는 공급자 중 하나를 사용하여 실제와 의미 체계 모두에 대한 문제를 이해해야 합니다.지워진 형식에 실제 .NET 형식이 없습니다.따라서 형식에 대한 정확한 리플렉션을 수행할 수 없으며 런타임 캐스트를 사용하고 다른 기술이 정확한 런타임 형식 의미 체계에 의존하는 경우 삭제된 형식을 되돌릴 수 있습니다.지워진 형식의 하위 버전을 사용할 경우 런타임 시 캐스팅 예외가 자주 발생합니다.

Hh361034.collapse_all(ko-kr,VS.110).gif공급된 지워진 형식의 표현 선택

지워진 공급된 형식을 사용할 경우 표현이 필요하지 않은 경우가 있습니다.예를 들어, 삭제된 공급자 형식은 정적 속성과 멤버만 포함할 수 있으며 생성자, 메서드 또는 속성은 형식 인스턴스를 반환하지 않습니다.지워진 공급 형식의 인스턴스에 도달할 수 있는 경우 다음 질문을 고려해야 합니다.

  • 제공된 형식의 지우기는 무엇입니까?

    • 제공된 형식의 지우기는 형식이 컴파일된 .NET 코드에 나타나는 방식입니다.

    • 제공된 삭제 클래스 형식의 지우기는 항상 형식의 상속 체인에서 첫 번째 삭제되지 않은 기본 형식입니다.

    • 제공된 지워진 인터페이스 형식의 지우기는 항상 Object입니다.

  • 제공된 형식의 표현은 무엇입니까?

    • 표현에서는 지워진 공급된 형식에 대해 가능한 개체의 집합을 호출합니다.이 문서의 예에서 제공된 형식 Type1..Type100의 모든 삭제된 표현은 항상 문자열 개체입니다.

제공된 형식의 모든 표현은 제공된 형식의 지우기와 맞아야 합니다.(그렇지 않으면 F# 컴파일러가 형식 공급자의 사용에 대해 오류를 제공하거나 확인할 수 없고 유효하지 않은 .NET 코드가 생성 됩니다.형식 공급자는 유효하지 않은 표현을 제공하는 코드를 반환하는 경우 사용할 수 없습니다.

둘 다 매우 공통적으로 다음 방법 중 하나를 사용하여 제공된 개체에 대한 표현을 선택할 수 있습니다.

  • 기존 .NET 형식에서 강력한 형식의 래퍼를 제공하는 경우 종종 사용자 형식에 대해 해당 형식을 삭제하거나 해당 형식의 인스턴스를 표현으로 사용하거나 둘 모두를 수행하는 것이 좋습니다.이 접근 방식은 해당 형식에 있는 대부분의 기존 메서드가 강력한 형식의 버전을 사용하면 의미가 있을 때 적합합니다.

  • 기존 .NET API와 상당히 다른 API를 만들고 싶으면 제공된 형식의 형식 지우기 및 표현이 될 런타임 형식을 만드는 것이 좋습니다.

이 문서의 예제는 문자열을 제공된 개체의 표현으로 사용합니다.표현에 다른 개체를 사용하는 것이 적합한 경우가 많습니다.예를 들어, 사전을 속성 모음으로 사용할 수 있습니다.

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

이 경우 ProvidedTypeDefinition을 생성할 때 이 형식을 baseType으로 지정하여 형식 지우기로 이 형식을 선택적으로 사용할 수 있습니다.

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

주요 단원

앞 단원에서는 광범위한 형식, 속성 및 메서드를 제공하는 간단한 지우기 형식 공급자를 만드는 방법을 설명했습니다.이 단원은 형식 공급자에서 지워진 형식을 제공할 경우의 몇 가지 장점 및 단점 등을 포함한 형식 지우기의 개념과 지워진 형식의 표현에 대해서도 설명합니다.

정적 매개 변수를 사용하는 형식 공급자

정적 데이터로 형식 공급자를 매개 변수화하는 기능을 사용하면 공급자가 로컬 또는 원격 데이터에 액세스할 필요가 없을 때라도 많은 흥미로운 시나리오가 가능합니다.이 단원에서는 이러한 공급자를 결합하는 몇 가지 기본 방법을 배웁니다.

Hh361034.collapse_all(ko-kr,VS.110).gif선택한 Regex 공급자 입력

다음 컴파일 타임을 제공하는 인터페이스에서 .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"

다음 사항에 유의하십시오.

  • 표준 Regex 형식은 매개 변수화된 RegexTyped 형식을 나타냅니다.

  • RegexTyped 생성자는 Regex 생성자를 호출하여 패턴을 위한 정적 형식 인수로 전달합니다.

  • Match 메서드의 결과는 표준 Match 형식에서 나타납니다.

  • 각 명명된 그룹은 제공된 속성에서 결과를 얻으며 속성에 액세스하면 일치의 Groups 컬렉션에 있는 인덱서를 사용하게 됩니다.

다음 코드는 이런 공급자를 구현하는 논리의 핵심이며 이 예제에는 제공된 형식에 대한 모든 멤버의 추가를 생략합니다.추가된 각 멤버에 대한 자세한 내용은 이 항목 뒷부분의 해당 단원을 참조하십시오.전체 코드의 경우 Codeplex 웹 사이트의 F# 3.0 Sample Pack에서 샘플을 다운로드합니다.

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의 형식이 잘못된 경우 예외를 throw하며 이 오류를 사용자에게 보고됩니다.

  • DefineStaticParameters 콜백 내에서 인수가 제공된 후 반환되는 형식을 정의합니다.

  • 이 코드는 IntelliSense 환경의 효율성을 높이도록 HideObjectMethods로 설정됩니다.이 특성은 제공된 개체에 대해 IntelliSense 목록에 Equals, GetHashCode, FinalizeGetType 멤버가 표시되지 않도록 합니다.

  • 다음 예제와 같이 obj를 메서드를 기본 형식으로 사용하지만 Regex 개체를 이 형식의 런타임 표현으로 사용하게 됩니다.

  • Regex 생성자에 대한 호출 시 정규식이 잘못되면 ArgumentException이 throw됩니다.컴파일러는 이 예외를 catch하고 컴파일 타임 또는 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 값으로 표현되므로 속성을 정의하는 큰따옴표는 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

생성자는 단순히 표준 .NET Regex 인스턴스의 만들기로 삭제되며, obj는 제공된 형식의 지우기이므로 개체에 다시 boxed됩니다.해당 변경 내용과 항목의 이전 부분에서 지정한 샘플 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를 제공하는 형식 공급자를 원할 수 있습니다.이 단원에서는 로컬 데이터 파일과 같은 로컬 데이터를 기반으로 하는 형식 공급자에 대해 설명합니다.

Hh361034.collapse_all(ko-kr,VS.110).gif간단한 CSV 파일 공급자

간단한 예로 쉼표로 구분된 값(CSV) 형식의 과학적 데이터에 액세스하기 위한 형식 공급자를 고려하는 것이 좋습니다.이 단원에서는 다음 표에서와 같이 CSV 파일에서 부동 소수점 데이터 뒤에 머리글 행을 포함하고 있다고 가정합니다.

거리(m)

시간(초)

50.0

3.7

100.0

5.2

150.0

6.4

이 단원에서는 float<meter> 형식의 Distance 속성과 float<second> 형식의 Time 속성으로 열을 가져오는 데 사용할 수 있는 형식을 제공하는 방법에 대해 배울 수 있습니다.편의상 다음 가정이 적용됩니다.

  • 헤더 이름은 단위가 없는 수이거나 “이름(단위)” 형식이며 쉼표를 포함하지 않습니다.

  • 단위는 모두 Microsoft.FSharp.Data.UnitSystems.SI.UnitNames Module (F#) 모듈을 정의할 때 SI(Systeme International) 단위입니다.

  • 단위는 모두 복합식(예: 미터/초)이 아닌 단순형(예: 미터)입니다.

  • 모든 열은 부동 소수점 데이터를 포함합니다.

더 많은 전체 공급자가 이 제한을 완화합니다.

다시, 첫 번째 단계는 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 형식을 사용하여 SI 단위를 조회하고 관련 float<_> 형식을 생성할 수 있습니다.

주요 단원

이 단원에서는 데이터 원본 자체에 포함된 간단한 스키마와 로컬 데이터 소스의 형식 공급자를 만드는 방법에 대해 설명했습니다.

추가 정보

다음 섹션에는 이 연습에서 다루지 않은 기능을 배울 수 있습니다.

Hh361034.collapse_all(ko-kr,VS.110).gif지워진 형식의 컴파일된 코드 살펴보기

형식 공급자의 사용이 내보내는 코드에 어떠한 방식으로 해당하는지에 대한 몇 가지 대안을 제시하려면 이 항목의 앞 부분에서 사용된 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 속성의 모든 언급은 삭제되어 런타임 형식의 작업만 포함되어 있습니다.

Hh361034.collapse_all(ko-kr,VS.110).gif형식 공급자의 디자인 및 명명 규칙

형식 공급자를 작성할 때 다음 규칙을 준수합니다.

  • 연결 프로토콜 공급자

    일반적으로 OData 또는 SQL 연결 같은 데이터와 서비스 연결 프로토콜을 위한 대부분의 공급자 DLL 이름은 TypeProvider 또는 TypeProviders로 끝나야 합니다.예를 들어, 다음 문자열과 유사한 DLL 이름을 사용합니다.

    Fabrikam.Management.BasicTypeProviders.dll
    

    제공된 형식이 해당 네임스페이스의 멤버이고 사용자가 구현한 연결 프로토콜을 나타내는지 확인합니다.

    Fabrikam.Management.BasicTypeProviders.WmiConnection<…>
    Fabrikam.Management.BasicTypeProviders.DataProtocolConnection<…>
    
  • 일반 코딩의 유틸리티 공급자

    정규식 같은 유틸리티 형식 공급자의 경우 형식 공급자는 다음 예에서 보여주듯이 기본 라이브러리의 일부일 수 있습니다.

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

    이 경우 제공된 형식은 표준 .NET 디자인 규약에 따라 적절한 지점에 나타납니다.

    open Fabrikam.Core.Text.RegexTyped
    
    let regex = new RegexTyped<"a+b+a+b+">()
    
  • Singleton 데이터 소스

    일부 형식 공급자는 하나의 전용 데이터 소스에 연결하고 데이터만 제공합니다.이 경우 TypeProvider 접미사를 놓아서 .NET 이름 지정 시 일반 규칙을 사용해야 합니다.

    #r "Fabrikam.Data.Freebase.dll"
    
    let data = Fabrikam.Data.Freebase.Astronomy.Asteroids
    

    자세한 내용은 이 항목의 뒷부분에서 설명하는 GetConnection 디자인 규칙을 참조하십시오.

Hh361034.collapse_all(ko-kr,VS.110).gif형식 공급자의 디자인 패턴

다음 단원에서는 형식 공급자를 작성할 때 사용할 수 있는 디자인 패턴에 대해 설명합니다.

Hh361034.collapse_all(ko-kr,VS.110).gifGetConnection 디자인 패턴

대부분의 형식 공급자는 다음 예제에서 볼 수 있듯이 FSharp.Data.TypeProviders.dll의 형식 공급자가 사용하는 GetConnection 패턴을 사용하도록 작성되어야 합니다.

#r "Fabrikam.Data.WebDataStore.dll"

type Service = Fabrikam.Data.WebDataStore<…static connection parameters…>

let connection = Service.GetConnection(…dynamic connection parameters…)

let data = connection.Astronomy.Asteroids

Hh361034.collapse_all(ko-kr,VS.110).gif원격 데이터 및 서비스로 지원되는 형식 공급자

원격 데이터와 서비스의 지원을 받는 형식 공급자를 만들기 전에 연결된 프로그래밍에 고유한 문제의 범위를 고려합니다.이러한 문제의 경우 사항을 고려해야 합니다.

  • 스키마 매핑

  • 스키마 변경 수준에서 실행 및 무효화

  • 스키마 캐싱

  • 데이터 액세스 작업의 비동기 구현

  • LINQ 쿼리를 포함하여 지원되는 쿼리

  • 자격 증명 및 인증

이 항목에서는 이러한 문제를 더 이상 탐색하지 않습니다.

Hh361034.collapse_all(ko-kr,VS.110).gif추가 작성 도구 기술

사용자 고유의 형식 공급자를 작성할 때 다음 추가 기술을 사용하려 할 수 있습니다.

  • 요청 시 형식 및 멤버 만들기

    ProvidedType API에는 AddMember의 지연된 버전이 있습니다.

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

    이러한 버전은 요청 시 형식의 공간을 만드는 데 사용됩니다.

  • 배열, ByRef 및 포인터 형식 제공

    ProvidedTypeDefinitions를 포함한 모든 System.Type 인스턴스에서 일반 MakeArrayType, MakePointerType 및 MakeGenericType을 사용하여 제공된 멤버를 만듭니다(멤버의 시그니처에는 배열 형식, byref 형식 및 일반 형식의 인스턴스화가 포함됨).

  • 측정 단위의 주석 제공

    ProvidedTypes API는 측정 주석을 공급하는 도우미를 제공합니다.예를 들어, 형식 float<kg>을(를) 제공하려면 다음 코드를 사용합니다.

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

    형식 Nullable<decimal<kg/m^2>>을(를) 제공하려면 다음 코드를 사용합니다.

    let kgpm2 = measures.Ratio(kg, measures.Square m)
    let dkgpm2 = measures.AnnotateType(typeof<decimal>,[kgpm2])
    let nullableDecimal_kgpm2 = typedefof<System.Nullable<_>>.MakeGenericType [|dkgpm2 |]
    
  • 프로젝트-로컬 또는 스크립트-로컬 리소스에 액세스하기

    형식 공급자의 각 인스턴스는 생성되는 동안 TypeProviderConfig 값을 지정할 수 있습니다.이 값에는 공급자에 대한 "해상도 폴더"(즉, 컴파일 대상의 프로젝트 폴더나 스크립트가 포함된 디렉터리), 참조된 어셈블리 목록 및 기타 정보가 들어 있습니다.

  • 무효화

    공급자는 스키마 가정이 변경되었음을 F# 언어 서비스에 알리기 위해 무효화 신호를 발생시킬 수 있습니다.무효화가 발생하면 공급자가 Visual Studio에 호스팅되는 경우 typecheck가 다시 실행됩니다.이 신호는 공급자가 F# 대화형 또는 F# 컴파일러(fsc.exe)에 의해 호스팅되는 경우 무시됩니다.

  • 스키마 정보 캐싱

    공급자는 스키마 정보에 대해 캐시에 액세스해야 하는 경우가 많습니다.캐싱된 데이터는 정적 매개 변수 또는 사용자 데이터로 제공된 파일 이름을 사용하여 저장해야 합니다.스키마 캐시의 예는 FSharp.Data.TypeProviders 어셈블리의 데이터 공급자에서 LocalSchemaFile 매개 변수입니다.이러한 공급자의 구현에서 이 정적 매개 변수는 네트워크를 통해 데이터 소스에 액세스하는 대신 지정된 로컬 파일에 있는 스키마 정보를 사용하도록 형식 공급자를 보냅니다.캐시된 스키마 정보를 사용하려면 정적 매개 변수 ForceUpdate 역시 false로 설정해야 합니다.온라인 및 오프라인 데이터 액세스를 활성화하기 위해 유사한 기법을 사용할 수 있습니다.

  • 어셈블리 지원

    .dll 또는 .exe 파일을 컴파일할 때 생성된 형식에 대한백업 .dll 파일이 결과 어셈블리에 정적으로 연결됩니다.이 링크는 지원 어셈블리의 임시 언어(IL) 형식 정의 및 관리되는 리소스를 최종 어셈블리에 복사하여 만들어집니다.F# Interactive를 사용하면 백업 .dll 파일이 복사되지 않으며 대신 F# Interactive 프로세스에 직접 로드됩니다.

  • 형식 공급자의 예외 및 진단

    제공된 형식에서 모든 멤버에 대한 모든 사용 시 예외를 throw할 수 있습니다.모든 경우에 형식 공급자가 예외를 throw하는 경우 호스트 컴파일러는 오류를 특정 형식 공급자에게 적용합니다.

    • 형식 공급자 예외 시 절대로 내부 컴파일러 오류를 일으키지 않아야 합니다.

    • 형식 공급자는 경고를 보고할 수 없습니다.

    • 형식 공급자가 F# 컴파일러, F# 개발 환경 또는 F# Interactive에서 호스팅되면 해당 공급자의 모든 예외가 catch됩니다.메시지 속성은 항상 오류 텍스트이며 스택 추적이 나타나지 않습니다.예외를 throw하려는 경우 다음 예제를 throw할 수 있습니다.

Hh361034.collapse_all(ko-kr,VS.110).gif생성된 형식 제공

지금까지 이 문서에서는 지워진 형식을 제공하는 방법을 설명했습니다.F#의 형식 공급자 메커니즘을 사용하여 생성된 형식을 제공할 수도 있습니다. 이 형식은 사용자의 프로그램에 실제 .NET 형식 정의로 추가됩니다.형식 정의를 사용하여 제공된 형식을 참조해야 합니다.

open Microsoft.FSharp.TypeProviders 

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

F# 3.0 릴리스의 일부인 ProvidedTypes-0.2 도우미 코드에는 생성된 형식을 제공하기 위한 제한적인 지원만 있습니다.다음 문은 생성된 형식 정의 대해 참이어야 합니다.

  • IsErased는 false로 설정해야 합니다.

  • 공급자는 디스크에 일치하는 .dll 파일이 있는 실제 지원 .NET .dll 파일이 포함된 어셈블리가 있어야 합니다.

ConvertToGenerated도 호출하여 루트에서 제공된 형식의 중첩된 형식이 생성된 닫힌 집합의 형식을 이루도록 해야 합니다.이 호출은 제공된 형식 정의와 중첩된 형식 정의를 어셈블리로 내보내고 해당 어셈블리로 반환하는 모든 제공된 형식 정의의 Assembly 속성을 조정합니다.어셈블리는 루트 형식의 Assembly 속성을 처음 액세스할 때만 내보내집니다.호스트 F# 컴파일러는 형식에 대한 생성 형식을 처리할 때 이 속성에 액세스합니다.

규칙 및 제한 사항

형식 공급자를 작성할 때는 다음 규칙과 제한 사항에 유의하십시오.

  • 공급된 형식에 연결할 수 있어야 합니다.

    모든 제공된 형식은 중첩되지 않은 형식에 연결할 수 있어야 합니다.중첩되지 않은 형식은 TypeProviderForNamespaces 생성자에 대한 호출이나 AddNamespace에 대한 호출에서 지정됩니다.예를 들어, 공급자가 StaticClass.P : T 형식을 제공하는 경우 T가 비중첩 형식인지 아래에 중첩된 형식인지 확인해야 합니다.

    예를 들어, 일부 공급자는 이러한 T1, T2, T3, ... 형식을 포함하는 DataTypes 같은 정적 클래스입니다.그렇지 않으면 어셈블리 A에서 형식 T에 대한 참조를 찾았지만 이 어셈블리에서 해당 형식을 찾을 수 없다는 오류가 표시됩니다.이 오류가 표시되면 모든 하위 형식을 공급자 형식에서 연결할 수 있는지 확인합니다.참고: 이 T1, T2, T3... 형식은 on-the-fly 형식으로 간주됩니다.액세스할 수 있는 네임스페이스 또는 부모 형식에 배치합니다.

  • 형식 공급자 메커니즘의 한계

    F#의 형식 공급자 메커니즘에는 다음과 같은 제한이 있습니다.

    • F#의 형식 공급자를 위한 기본 인프라는 제네릭 형식이나 제공된 제네릭 메서드를 지원하지 않습니다.

    • 메커니즘은 정적 매개 변수와 함께 중첩 형식을 지원하지 않습니다.

  • ProvidedTypes 지원 코드 제한

    ProvidedTypes 지원 코드에는 다음과 같은 규칙과 제한이 있습니다:

    1. 인덱싱된 getter 및 setter를 사용하여 공급된 속성은 구현되지 않습니다.

    2. 공급된 이벤트는 구현되지 않습니다.

    3. 제공된 형식 및 정보 개체는 F#에서 형식 공급자 메커니즘에 대해서만 사용되어야 합니다.일반적으로 System.Type 개체로 더 이상 사용할 수 없습니다.

    4. 메서드 구현을 정의하고 인용구를 사용할 수 있는는 구문에 몇 가지 제한 사항이 있습니다.ProvidedTypes-Version의 소스 코드를 참조하여 어떤 구문이 따옴표와 함께 지원되는지를 확인할 수 있습니다.

  • 형식 공급자는.dll 파일,.exe 파일이 아닌 출력 어셈블리를 생성 해야 합니다.

개발 팁

개발 프로세스 중에 유용할 수 있는 다음 팁을 찾을 수 있습니다.

  • Visual Studio에 대한 2개의 인스턴스를 실행합니다. 형식 공급자를 하나의 인스턴스에 개발하고 형식 공급자가 다시 작성되지 않도록 테스트 IDE에서 .dll 파일에 대해 잠금을 설정하기 때문에 다른 공급자를 테스트할 수 있습니다.따라서 공급자가 첫 번째 인스턴스에서 빌드되는 동안 Visual Studio의 두 번째 인스턴스를 닫은 후 공급자가 빌드되면 두 번째 인스턴스를 다시 열어야 합니다.

  • 형식 공급자는 fsc.exe의 호출을 통해 디버깅합니다. 다음과 같은 도구를 사용하여 형식 공급자를 호출할 수 있습니다.

    • fsc.exe(F# 명령줄 컴파일러)

    • fsi.exe(F# Interactive 컴파일러)

    • devenv.exe(Visual Studio)

    종종 fsc.exe 테스트 스크립트 파일(예: script.fsx)을 사용하여 형식 공급자를 가장 쉽게 디버깅할 수 있습니다.명령 프롬프트에서 디버거를 시작할 수 있습니다.

    devenv /debugexe fsc.exe script.fsx
    

    print-to-stdout 로깅을 사용할 수 있습니다.

참고 항목

기타 리소스

형식 공급자