클래스(F#)
클래스는 속성, 메서드 및 이벤트를 가질 수 있는 개체를 나타내는 형식입니다.
// Class definition:
type [access-modifier] type-name [type-params] [access-modifier] ( parameter-list ) [ as identifier ] =
[ class ]
[ inherit base-type-name(base-constructor-args) ]
[ let-bindings ]
[ do-bindings ]
member-list
...
[ end ]
// Mutually recursive class definitions:
type [access-modifier] type-name1 ...
and [access-modifier] type-name2 ...
...
설명
클래스는 .NET 개체 형식에 대한 기본 설명을 제공합니다. 클래스는 F#의 개체 지향 프로그래밍을 지원하는 기본 형식 개념입니다.
위 구문에서 type-name은 임의의 유효한 식별자입니다. type-params는 선택적 제네릭 형식 매개 변수를 설명합니다. 이는 형식 매개 변수 이름과 꺾쇠 괄호(< 및 >)로 묶은 제약 조건으로 구성됩니다. 자세한 내용은 제네릭(F#) 및 제약 조건(F#)을 참조하십시오. parameter-list는 생성자 매개 변수를 설명합니다. 첫 번째 액세스 한정자는 유형과 관련되어 있으며, 두 번째는 기본 생성자와 관련되어 있습니다. 두 경우 모두 기본값은 public입니다.
클래스의 기본 클래스를 지정하는 데는 inherit 키워드를 사용합니다. 기본 클래스 생성자의 인수는 괄호로 묶어 제공해야 합니다.
클래스에 로컬인 필드나 함수 값을 선언하는 데는 let 바인딩을 사용합니다. 이때 let 바인딩에 관련된 일반 규칙을 따라야 합니다. do-bindings 섹션에는 개체 생성 시 실행할 코드가 포함됩니다.
member-list는 추가 생성자, 인스턴스 및 정적 메서드 선언, 인터페이스 선언, 추상 바인딩, 속성 및 이벤트 선언으로 구성됩니다. 이러한 각 항목에 대한 자세한 내용은 멤버(F#)를 참조하십시오.
identifier를 선택적 as 키워드와 함께 사용하면 인스턴스 변수의 이름, 즉 자체 식별자를 부여할 수 있습니다. 형식 정의에 이 이름을 사용하여 형식의 인스턴스를 참조할 수 있습니다. 자세한 내용은 이 항목 뒷부분에 나오는 자체 식별자 단원을 참조하십시오.
정의 시작과 끝을 표시하는 class 및 end 키워드는 선택적 요소입니다.
서로를 참조하는 형식인 상호 재귀 형식은 상호 재귀 함수의 경우와 마찬가지로 and 키워드를 사용하여 연결됩니다. 구체적인 예는 상호 재귀 형식 단원을 참조하십시오.
생성자
생성자는 클래스 형식의 인스턴스를 만드는 코드입니다. 클래스의 생성자가 작동하는 방식은 F#과 기타 .NET 언어 사이에 약간 차이가 있습니다. F# 클래스에는 항상 형식 이름 뒤에 오는 parameter-list에서 해당 인수를 설명하는 기본 생성자가 있어야 합니다. 기본 생성자의 본문은 클래스 선언 시작 부분의 let(및 let rec) 바인딩과 그 뒤를 따르는 do 바인딩으로 이루어집니다. 기본 생성자 인수의 범위는 클래스 선언 전체를 포함합니다.
다음과 같이 new 키워드로 멤버를 추가하여 다른 생성자를 더 추가할 수 있습니다.
new(argument-list) = constructor-body
새로 추가한 생성자의 본문에서는 클래스 선언 맨 위에 지정되어 있는 기본 생성자를 호출해야 합니다.
다음 예제에서는 이 개념을 보여 줍니다. 다음 코드에서 MyClass에는 두 개의 생성자가 있습니다. 그중 하나는 인수 두 개를 취하는 기본 생성자이고 다른 하나는 인수를 전혀 취하지 않는 생성자입니다.
type MyClass1(x: int, y: int) =
do printfn "%d %d" x y
new() = MyClass1(0, 0)
자세한 내용은 생성자(F#)를 참조하십시오.
let 및 do 바인딩
클래스 정의에서 let 및 do 바인딩은 기본 클래스 생성자의 본문을 구성하므로 클래스 인스턴스를 만들 때마다 실행됩니다. let 바인딩이 함수이면 컴파일 결과는 멤버가 됩니다. let 바인딩이 함수나 멤버에 전혀 사용되지 않는 값이면 컴파일 결과는 생성자에 로컬인 변수가 됩니다. 그 밖의 경우에는 컴파일 결과가 클래스의 필드가 됩니다. 다음에 나오는 do 식은 기본 생성자로 컴파일되고 모든 인스턴스에 대해 초기화 코드를 실행합니다. 모든 추가 생성자는 항상 기본 생성자를 호출하므로 let 바인딩과 do 바인딩은 호출 대상 생성자가 무엇이든 상관없이 항상 실행됩니다.
let 바인딩을 사용하여 만든 필드에는 클래스의 모든 메서드와 속성을 통해 액세스할 수 있습니다. 그러나 정적 메서드에서는 그와 같은 필드에 액세스할 수 없습니다. 이는 정적 메서드에서 인스턴스 변수를 매개 변수로 취하는 경우라 해도 마찬가지입니다. 또한 자체 식별자가 있더라도 이를 사용하여 해당 필드에 액세스할 수도 없습니다.
자체 식별자
자체 식별자는 현재 인스턴스를 나타내는 이름입니다. 자체 식별자는 C# 또는 C++의 this 키워드나 Visual Basic의 Me와 비슷합니다. 자체 식별자의 범위를 전체 클래스 정의로 할지 개별 메서드로만 국한할지에 따라 서로 다른 두 가지 방법 중 하나로 자체 식별자를 정의할 수 있습니다.
전체 클래스에 대해 자체 식별자를 정의하려면 생성자 매개 변수 목록의 닫는 괄호 뒤에 as 키워드를 사용하고 식별자 이름을 지정합니다.
메서드 하나에 대해서만 자체 식별자를 정의하려면 멤버 선언에서 메서드 이름 및 구분 기호인 마침표(.) 바로 앞에 자체 식별자를 추가합니다.
다음 코드 예제에서는 자체 식별자를 만드는 두 가지 방법을 보여 줍니다. 첫 줄에서는 as 키워드를 사용하여 자체 식별자를 정의합니다. 다섯째 줄에서는 식별자 this를 사용하여 해당 범위가 PrintMessage 메서드로 제한된 자체 식별자를 정의합니다.
type MyClass2(dataIn) as self =
let data = dataIn
do
self.PrintMessage()
member this.PrintMessage() =
printf "Creating MyClass2 with Data %d" data
다른 .NET 언어와 달리 자체 식별자의 이름을 원하는 대로 지정할 수 있습니다. 즉, self, Me 또는 this 같은 이름 이외에 다른 이름도 사용할 수 있습니다.
as 키워드로 선언한 자체 식별자는 let 바인딩이 실행되고 나야 초기화됩니다. 따라서 let 바인딩에는 자체 식별자를 사용할 수 없습니다. 그러나 do 바인딩 섹션에는 자체 식별자를 사용할 수 있습니다.
제네릭 형식 매개 변수
제네릭 형식 매개 변수는 꺾쇠 괄호(< 및 >)로 지정하며 작은따옴표 뒤에 식별자를 추가하는 형식이 사용됩니다. 제네릭 형식 매개 변수가 여러 개이면 각 매개 변수를 쉼표로 구분합니다. 제네릭 형식 매개 변수의 범위는 선언 전체를 대상으로 합니다. 다음 코드 예제에서는 제네릭 형식 매개 변수를 지정하는 방법을 보여 줍니다.
type MyGenericClass<'a> (x: 'a) =
do printfn "%A" x
형식을 사용하는 경우 형식 인수가 유추됩니다. 다음 코드에서 유추되는 형식은 튜플의 시퀀스입니다.
let g1 = MyGenericClass( seq { for i in 1 .. 10 -> (i, i*i) } )
상속 지정
inherit 절은 직접 기본 클래스가 있는 경우 이를 식별합니다. F#에서 직접 기본 클래스는 한 개만 허용됩니다. 클래스에서 구현하는 인터페이스는 기본 클래스로 간주되지 않습니다. 인터페이스에 대한 설명은 인터페이스(F#) 항목을 참조하십시오.
언어 키워드 base를 식별자로 사용하고 그 뒤에 마침표(.)와 멤버 이름을 추가하여 파생 클래스에서 기본 클래스의 메서드와 속성에 액세스할 수 있습니다.
자세한 내용은 상속(F#)을 참조하십시오.
멤버 섹션
이 섹션에서 정적 메서드나 인스턴스 메서드, 속성, 인터페이스 구현, 추상 멤버, 이벤트 선언 및 추가 생성자를 정의할 수 있습니다. let 바인딩과 do 바인딩은 이 섹션에 사용할 수 없습니다. 클래스뿐 아니라 다양한 F# 형식에 멤버를 추가할 수 있으므로 이들 요소에 대해서는 멤버(F#) 항목에서 별도로 설명합니다.
상호 재귀 형식
순환 방식으로 서로를 참조하는 형식을 정의하는 경우 and 키워드를 사용하여 형식 정의를 함께 묶어야 합니다. and 키워드는 다음과 같이 첫째 정의를 제외한 모든 정의에서 type 키워드를 대신합니다.
open System.IO
type Folder(pathIn: string) =
let path = pathIn
let filenameArray : string array = Directory.GetFiles(path)
member this.FileArray = Array.map (fun elem -> new File(elem, this)) filenameArray
and File(filename: string, containingFolder: Folder) =
member this.Name = filename
member this.ContainingFolder = containingFolder
let folder1 = new Folder(".")
for file in folder1.FileArray do
printfn "%s" file.Name
현재 디렉터리에 있는 모든 파일의 목록이 출력됩니다.
클래스, 공용 구조체, 레코드 및 구조체의 선택 기준
선택할 수 있는 형식이 다양하므로 특정 상황에 맞는 형식을 제대로 선택하기 위해서는 각 형식의 설계 목적을 이해할 필요가 있습니다. 클래스는 개체 지향 프로그래밍 컨텍스트에 사용하기 위한 것입니다. 개체 지향 프로그래밍은 .NET Framework용으로 작성되는 응용 프로그램에 주로 사용되는 패러다임입니다. F# 코드와 .NET Framework 또는 기타 개체 지향 라이브러리 사이에 긴밀한 상호 작용이 필요한 경우, 특히 UI 라이브러리 같은 개체 지향 형식 시스템을 확장해야 하는 경우에는 클래스를 선택하는 것이 적절할 수 있습니다.
개체 지향 코드와의 긴밀한 상호 작용이 필요하지 않거나 자체 충족적이어서 개체 지향 코드와의 빈번한 상호 작용으로부터 보호를 받는 코드를 작성하려는 경우에는 레코드 및 구별된 공용 구조체의 사용을 고려해 보는 것이 좋습니다. 충분히 숙고하고 잘 계획하여 작성한 구별된 공용 구조체 하나를 적절한 패턴 일치 코드와 함께 사용하면 개체 계층 구조를 대신하는 더 간단한 코드를 만들 수 있습니다. 구별된 공용 구조체에 대한 자세한 내용은 Discriminated Unions(F#)를 참조하십시오.
레코드는 클래스보다 더 단순하다는 이점이 있지만 형식의 요구 사항이 많아서 그 단순함으로 해결이 되지 않는 경우에는 레코드를 사용하는 것이 적절하지 않습니다. 기본적으로 레코드는 사용자 지정 작업을 수행할 수 있는 별도의 생성자가 없고, 숨겨진 파일도 없으며, 상속 또는 인터페이스 구현도 없는 단순한 값 집계입니다. 속성이나 메서드 같은 멤버를 레코드에 추가하여 그 동작을 좀 더 복잡하게 만들 수는 있지만 레코드에 저장되는 필드는 여전히 값의 단순한 집계에 불과합니다. 레코드에 대한 자세한 내용은 레코드(F#)를 참조하십시오.
구조체도 데이터의 소규모 집계에 유용하게 사용할 수 있지만 구조체는 .NET 값 형식이라는 점에서 클래스나 레코드와 다릅니다. 반면, 클래스와 레코드는 .NET 참조 형식입니다. 값 형식과 참조 형식의 의미론은 값 형식의 경우 해당 형식이 값을 통해 전달된다는 점에서 차이가 있습니다. 즉, 매개 변수로 전달되거나 함수로부터 반환되는 값 형식은 비트 수준에 이르기까지 그대로 복사됩니다. 또한 값 형식은 스택에 저장되거나, 필드로 사용되는 값 형식의 경우 힙의 고유한 별도 위치에 저장되지 않고 개체 안에 포함됩니다. 따라서 힙 액세스로 인한 오버헤드가 문제가 되는 경우 자주 액세스하는 데이터에 대해서는 구조체를 사용하는 것이 좋습니다. 구조체에 대한 자세한 내용은 구조체(F#)를 참조하십시오.