about_Classes_Constructors
about_Classes_Constructors (클래스 생성자)
PowerShell 클래스에서 **생성자(Constructor)**를 어떻게 정의하는지 알려드릴게요. 생성자를 제대로 다루면 객체가 만들어지는 순간 바로 초깃값을 채우고, 유효성을 검증하는 일을 깔끔하게 처리할 수 있어요. 생성자는 클래스와 같은 이름을 가진다는 점이 핵심이고, 여기서는 기본 생성자부터 오버로드, 상속, 체이닝까지 차근차근 살펴봐요.
본문
짧은 설명
PowerShell 클래스에 생성자를 정의하는 방법을 다룹니다.
자세한 설명
생성자는 클래스 인스턴스를 만드는 순간 기본값을 설정하고 객체 로직을 검증할 수 있게 해 줘요. 생성자는 클래스와 이름이 같고, 새 객체의 데이터 멤버를 초기화하기 위해 매개 변수를 가질 수도 있어요.
PowerShell 클래스 생성자는 클래스 위에 정의되는 특별한 메서드예요. 일반적인 PowerShell 클래스 메서드와 같은 방식으로 동작하지만, 다음 차이점이 있어요.
- 생성자는 출력 형식(output type)이 없어요.
return키워드를 쓸 수 없어요.- 생성자는 항상 클래스와 같은 이름이에요.
- 생성자는 직접 호출할 수 없어요. 인스턴스가 만들어질 때만 실행돼요.
- 생성자는
Get-Membercmdlet 출력에 나타나지 않아요.
PowerShell 클래스 메서드에 대한 자세한 내용은 about_Classes_Methods 문서를 참고해요.
클래스에는 생성자를 0개 또는 여러 개 정의할 수 있어요. 생성자를 하나도 정의하지 않으면 클래스에 **매개 변수가 없는 기본 생성자(default parameterless constructor)**가 자동으로 생겨요. 이 생성자는 모든 멤버를 기본값으로 초기화하는데, 객체 형식과 문자열은 null 값이 되고요. 만약 생성자를 직접 정의하면 자동 기본 생성자는 만들어지지 않으니, 필요하다면 매개 변수 없는 생성자를 직접 만들어 줘야 해요.
매개 변수가 없는 정적 생성자도 정의할 수 있어요.
구문(Syntax)
클래스 생성자는 다음과 같은 구문을 사용해요.
기본 생성자 구문
<class-name> () [: base([<params>])] {
<body>
}
정적 생성자 구문
static <class-name> () [: base([<params>])] {
<body>
}
매개 변수가 있는 생성자 구문(한 줄)
<class-name> ([[<parameter-type>]$<parameter-name>[, [<parameter-type>]$<parameter-name>...]]) [: base([<params>])] {
<body>
}
매개 변수가 있는 생성자 구문(여러 줄)
<class-name> (
[<parameter-type>]$<parameter-name>[,
[<parameter-type>]$<parameter-name>...]
) [: base([<params>])] {
<body>
}
예제
예제 1 - 기본 생성자를 사용하는 클래스 정의하기
ExampleBook1 클래스는 생성자를 정의하지 않아요. 대신 자동으로 제공되는 기본 생성자를 사용해요.
class ExampleBook1 {
[string] $Name
[string] $Author
[int] $Pages
[datetime] $PublishedOn
}
[ExampleBook1]::new()
Name Author Pages PublishedOn
---- ------ ----- -----------
0 1/1/0001 12:00:00 AM
참고
Name과Author속성은 문자열(참조 형식)이라 기본값이$null이에요. 나머지 속성은 값 형식이라 각자 형식에 맞는 기본값을 가져요. 속성 기본값에 대한 자세한 내용은 about_Classes_Properties 문서의 "Default property values" 부분을 참고해요.
예제 2 - 기본 생성자 재정의하기
ExampleBook2는 기본 생성자를 명시적으로 정의해서, PublishedOn은 현재 날짜로, Pages는 1로 설정해요.
class ExampleBook2 {
[string] $Name
[string] $Author
[int] $Pages
[datetime] $PublishedOn
ExampleBook2() {
$this.PublishedOn = (Get-Date).Date
$this.Pages = 1
}
}
[ExampleBook2]::new()
Name Author Pages PublishedOn
---- ------ ----- -----------
1 11/1/2023 12:00:00 AM
예제 3 - 생성자 오버로드 정의하기
ExampleBook3 클래스는 세 가지 생성자 오버로드를 정의해요. 덕분에 해시 테이블로 인스턴스를 만들 수도 있고, 모든 속성 값을 하나씩 넘길 수도 있으며, 책 이름과 저자만 넘겨서 만들 수도 있어요. 이 클래스는 기본 생성자를 정의하지 않아요.
class ExampleBook3 {
[string] $Name
[string] $Author
[int] $Pages
[datetime] $PublishedOn
ExampleBook3([hashtable]$Info) {
switch ($Info.Keys) {
'Name' { $this.Name = $Info.Name }
'Author' { $this.Author = $Info.Author }
'Pages' { $this.Pages = $Info.Pages }
'PublishedOn' { $this.PublishedOn = $Info.PublishedOn }
}
}
ExampleBook3(
[string] $Name,
[string] $Author,
[int] $Pages,
[datetime] $PublishedOn
) {
$this.Name = $Name
$this.Author = $Author
$this.Pages = $Pages
$this.PublishedOn = $PublishedOn
}
ExampleBook3([string]$Name, [string]$Author) {
$this.Name = $Name
$this.Author = $Author
}
}
[ExampleBook3]::new(@{
Name = 'The Hobbit'
Author = 'J.R.R. Tolkien'
Pages = 310
PublishedOn = '1937-09-21'
})
[ExampleBook3]::new('The Hobbit', 'J.R.R. Tolkien', 310, '1937-09-21')
[ExampleBook3]::new('The Hobbit', 'J.R.R. Tolkien')
[ExampleBook3]::new()
Name Author Pages PublishedOn
---- ------ ----- -----------
The Hobbit J.R.R. Tolkien 310 9/21/1937 12:00:00 AM
The Hobbit J.R.R. Tolkien 310 9/21/1937 12:00:00 AM
The Hobbit J.R.R. Tolkien 0 1/1/0001 12:00:00 AM
MethodException:
Line |
42 | [ExampleBook3]::new()
| ~~~~~~~~~~~~~~~~~~~~~
| Cannot find an overload for "new" and the argument count: "0".
기본 생성자를 호출하면 메서드 예외가 발생해요. 자동 기본 생성자는 클래스가 아무 생성자도 정의하지 않았을 때만 생기거든요. ExampleBook3는 오버로드를 여러 개 정의했기 때문에 기본 생성자가 자동으로 추가되지 않는 거예요.
예제 4 - 공유 메서드로 생성자 체이닝 흉내 내기
이 예제는 생성자에서 재사용할 수 있는 공통 코드를 어떻게 만드는지 보여줘요. PowerShell 클래스는 생성자 체이닝을 지원하지 않으니, 이 예제 클래스는 대신 Init() 메서드를 정의했어요. 이 메서드에는 오버로드가 여러 개 있는데, 매개 변수가 적은 오버로드가 지정되지 않은 매개 변수에 기본값을 넣어 더 명시적인 오버로드를 호출하는 방식이에요.
class ExampleBook4 {
[string] $Name
[string] $Author
[datetime] $PublishedOn
[int] $Pages
ExampleBook4() {
$this.Init()
}
ExampleBook4([string]$Name) {
$this.Init($Name)
}
ExampleBook4([string]$Name, [string]$Author) {
$this.Init($Name, $Author)
}
ExampleBook4([string]$Name, [string]$Author, [datetime]$PublishedOn) {
$this.Init($Name, $Author, $PublishedOn)
}
ExampleBook4(
[string]$Name,
[string]$Author,
[datetime]$PublishedOn,
[int]$Pages
) {
$this.Init($Name, $Author, $PublishedOn, $Pages)
}
hidden Init() {
$this.Init('Unknown')
}
hidden Init([string]$Name) {
$this.Init($Name, 'Unknown')
}
hidden Init([string]$Name, [string]$Author) {
$this.Init($Name, $Author, (Get-Date).Date)
}
hidden Init([string]$Name, [string]$Author, [datetime]$PublishedOn) {
$this.Init($Name, $Author, $PublishedOn, 1)
}
hidden Init(
[string]$Name,
[string]$Author,
[datetime]$PublishedOn,
[int]$Pages
) {
$this.Name = $Name
$this.Author = $Author
$this.PublishedOn = $PublishedOn
$this.Pages = $Pages
}
}
[ExampleBook4]::new()
[ExampleBook4]::new('The Hobbit')
[ExampleBook4]::new('The Hobbit', 'J.R.R. Tolkien')
[ExampleBook4]::new('The Hobbit', 'J.R.R. Tolkien', (Get-Date '1937-9-21'))
[ExampleBook4]::new(
'The Hobbit',
'J.R.R. Tolkien',
(Get-Date '1937-9-21'),
310
)
Name Author PublishedOn Pages
---- ------ ----------- -----
Unknown Unknown 11/1/2023 12:00:00 AM 1
The Hobbit Unknown 11/1/2023 12:00:00 AM 1
The Hobbit J.R.R. Tolkien 11/1/2023 12:00:00 AM 1
The Hobbit J.R.R. Tolkien 9/21/1937 12:00:00 AM 1
The Hobbit J.R.R. Tolkien 9/21/1937 12:00:00 AM 310
예제 5 - 파생 클래스의 생성자
아래 예제들은 기본 클래스와 그 기본 클래스를 상속받는 파생 클래스에 대해 정적 생성자, 기본 생성자, 매개 변수가 있는 생성자를 정의하는 모습을 보여줘요.
class BaseExample {
static [void] DefaultMessage([type]$Type) {
Write-Verbose "[$($Type.Name)] default constructor"
}
static [void] StaticMessage([type]$Type) {
Write-Verbose "[$($Type.Name)] static constructor"
}
static [void] ParamMessage([type]$Type, [Object]$Value) {
Write-Verbose "[$($Type.Name)] param constructor ($Value)"
}
static BaseExample() { [BaseExample]::StaticMessage([BaseExample]) }
BaseExample() { [BaseExample]::DefaultMessage([BaseExample]) }
BaseExample($Value) { [BaseExample]::ParamMessage([BaseExample], $Value) }
}
class DerivedExample : BaseExample {
static DerivedExample() { [BaseExample]::StaticMessage([DerivedExample]) }
DerivedExample() { [BaseExample]::DefaultMessage([DerivedExample]) }
DerivedExample([int]$Number) : base($Number) {
[BaseExample]::ParamMessage([DerivedExample], $Number)
}
DerivedExample([string]$String) {
[BaseExample]::ParamMessage([DerivedExample], $String)
}
}
아래 블록은 기본 클래스 생성자를 호출할 때 나오는 세부 메시지(verbose)를 보여줘요. 정적 생성자 메시지는 클래스 인스턴스가 처음 만들어질 때 한 번만 출력돼요.
PS> $VerbosePreference = 'Continue'
PS> $b = [BaseExample]::new()
VERBOSE: [BaseExample] static constructor
VERBOSE: [BaseExample] default constructor
PS> $b = [BaseExample]::new()
VERBOSE: [BaseExample] default constructor
PS> $b = [BaseExample]::new(1)
VERBOSE: [BaseExample] param constructor (1)
다음 블록은 새 세션에서 파생 클래스 생성자를 호출할 때 나오는 세부 메시지를 보여줘요. 파생 클래스 생성자가 처음 호출되면 기본 클래스와 파생 클래스의 정적 생성자가 함께 호출돼요. 이 정적 생성자들은 세션 안에서 다시 호출되지 않아요. 그리고 기본 클래스의 생성자는 항상 파생 클래스의 생성자보다 먼저 실행돼요.
PS> $VerbosePreference = 'Continue'
PS> $c = [DerivedExample]::new()
VERBOSE: [BaseExample] static constructor
VERBOSE: [DerivedExample] static constructor
VERBOSE: [BaseExample] default constructor
VERBOSE: [DerivedExample] default constructor
PS> $c = [DerivedExample]::new()
VERBOSE: [BaseExample] default constructor
VERBOSE: [DerivedExample] default constructor
PS> $c = [DerivedExample]::new(1)
VERBOSE: [BaseExample] param constructor (1)
VERBOSE: [DerivedExample] param constructor (1)
PS> $c = [DerivedExample]::new('foo')
VERBOSE: [BaseExample] default constructor
VERBOSE: [DerivedExample] param constructor (foo)
생성자 실행 순서
클래스가 인스턴스화되면 하나 이상의 생성자 코드가 실행돼요.
다른 클래스를 상속하지 않는 클래스라면 순서는 이렇게 돼요.
- 클래스의 정적 생성자
- 클래스에 해당하는 생성자 오버로드
다른 클래스를 상속받는 파생 클래스라면 순서는 이렇게 돼요.
- 파생 클래스의 정적 생성자가 기본 클래스에 의존하지 않으면, 파생 클래스의 정적 생성자가 먼저 호출돼요.
- 파생 클래스의 정적 생성자가 기본 클래스에 의존하면, 기본 클래스의 정적 생성자가 먼저 실행된 뒤에 파생 클래스에서 기본 클래스에 의존하는 줄이 실행돼요.
- 파생 클래스 생성자가 기본 생성자 오버로드를 명시적으로 호출하면 그 오버로드가 실행돼요. 명시적으로 호출하지 않으면 파생 클래스는 기본 클래스의 기본 생성자를 실행해요.
- 파생 클래스에 해당하는 생성자 오버로드
어떤 경우든 정적 생성자는 세션 안에서 한 번만 실행돼요.
생성자 동작과 순서에 대한 예제는 예제 5를 참고해요.
숨겨진 생성자(Hidden constructors)
hidden 키워드로 생성자를 숨길 수 있어요. 숨겨진 클래스 생성자는 다음과 같은 특징이 있어요.
- 클래스의 기본 출력에 포함되지 않아요.
Get-Membercmdlet이 반환하는 클래스 멤버 목록에 포함되지 않아요.Get-Member로 숨겨진 속성을 보려면Force매개 변수를 사용해요.- 탭 완성이나 IntelliSense에 표시되지 않아요. 단, 숨겨진 속성을 정의한 클래스 안에서 완성이 일어날 때는 예외예요.
- 클래스의 공용 멤버예요. 접근하고 수정할 수 있죠. 속성을 숨긴다고 private이 되는 건 아니에요. 앞서 설명한 내용대로 단지 표시만 숨기는 거예요.
참고
생성자를 하나라도 숨기면 IntelliSense와 완성 결과에서
new()옵션이 사라져요.
hidden 키워드에 대한 자세한 내용은 about_Hidden 문서를 참고해요.
정적 생성자(Static constructors)
static 키워드로 생성자를 정의하면, 인스턴스가 아니라 클래스 자체에 속하는 생성자가 돼요. 정적 클래스 생성자는 다음 특징이 있어요.
- 세션 안에서 클래스 인스턴스가 처음 만들어질 때 한 번만 호출돼요.
- 매개 변수를 가질 수 없어요.
$this변수로 인스턴스 속성이나 메서드에 접근할 수 없어요.
파생 클래스의 생성자
클래스가 다른 클래스를 상속받으면, 생성자에서 base 키워드로 기본 클래스의 생성자를 호출할 수 있어요. 파생 클래스가 기본 클래스의 생성자를 명시적으로 호출하지 않으면, 대신 기본 클래스의 기본 생성자를 호출해요.
기본 생성자가 아닌 생성자를 호출하려면, 생성자 매개 변수 뒤, body 블록 앞에 : base(<parameters>)를 추가하면 돼요.
class <derived-class> : <base-class> {
<derived-class>(<derived-parameters>) : <base>(<base-parameters>) {
# initialization code
}
}
기본 클래스 생성자를 호출하는 생성자를 정의할 때, 매개 변수로 다음 항목 중 무엇이든 쓸 수 있어요.
- 파생 클래스 생성자의 어떤 매개 변수 변수든 사용 가능
- 어떤 정적 값이든 사용 가능
- 매개 변수 형식의 값으로 평가되는 어떤 표현식이든 사용 가능
파생 클래스 생성자 예제는 예제 5를 참고해요.
생성자 체이닝(Chaining constructors)
C#과 달리 PowerShell 클래스 생성자는 : this(<parameters>) 구문으로 체이닝을 쓸 수 없어요. 코드 중복을 줄이려면, 같은 효과를 내는 여러 오버로드를 가진 hidden Init() 메서드를 사용해요. 이 패턴을 사용하는 예제가 예제 4에 나와 있어요.
Update-TypeData로 인스턴스 속성과 메서드 추가하기
클래스 정의에서 직접 속성과 메서드를 선언하는 것 외에도, 정적 생성자에서 Update-TypeData cmdlet을 사용해 클래스 인스턴스의 속성을 정의할 수 있어요.
이 스니펫을 패턴의 시작점으로 사용하면 돼요. 각괄호 안의 플레이스홀더 텍스트는 상황에 맞게 바꿔 주세요.
class <class-name> {
static [hashtable[]] $MemberDefinitions = @(
@{
Name = '<member-name>'
MemberType = '<member-type>'
Value = <member-definition>
}
)
static <class-name>() {
$TypeName = [<class-name>].Name
foreach ($Definition in [<class-name>]::MemberDefinitions) {
Update-TypeData -TypeName $TypeName @Definition
}
}
}
팁
Add-Membercmdlet으로도 non-static 생성자에서 클래스에 속성과 메서드를 추가할 수 있지만, 이 cmdlet은 생성자가 호출될 때마다 실행돼요. 정적 생성자에서Update-TypeData를 쓰면 클래스에 멤버를 추가하는 코드가 세션 안에서 한 번만 실행되면 된다는 장점이 있어요.non-static 생성자에서는
Update-TypeData로 정의할 수 없는 경우, 예를 들어 읽기 전용 속성 같은 걸 추가할 때만 클래스에 속성을 넣어 주세요.
Update-TypeData로 인스턴스 메서드를 정의하는 방법은 about_Classes_Methods 문서를, 인스턴스 속성을 정의하는 방법은 about_Classes_Properties 문서를 참고해요.
제한 사항(Limitations)
PowerShell 클래스 생성자에는 다음 제한 사항이 있어요.
- 생성자 체이닝은 구현되어 있지 않아요. 해결 방법:
hiddenInit()메서드를 정의하고 생성자 안에서 호출해요. - 생성자 매개 변수는 검증 속성(validation attributes)을 포함한 어떤 특성도 쓸 수 없어요. 해결 방법: 생성자 body 안에서 검증 속성이 있는 매개 변수로 다시 할당해요.
- 생성자 매개 변수는 기본값을 정의할 수 없어요. 매개 변수는 항상 필수(mandatory)예요. 해결 방법: 없음.
- 생성자의 오버로드 중 하나라도 숨겨지면, 해당 생성자의 모든 오버로드가 숨겨진 것으로 취급돼요. 해결 방법: 없음.