about_PSCustomObject

about_PSCustomObject

[psobject][pscustomobject] 두 타입 액셀러레이터는 이름이 비슷해 보여서, 언뜻 같은 걸 가리키는 것처럼 느껴지기 쉬워요. 그런데 실제로는 역할이 조금씩 달라요. 이 문서에서는 두 타입 액셀러레이터가 어떻게 다르고, 언제 [pscustomobject]를 쓰는 게 좋은지 하나씩 짚어볼게요.

출처: about_PSCustomObject — Microsoft Learn

본문

[psobject][pscustomobject]는 어떻게 다를까

[pscustomobject] 타입 액셀러레이터는 PowerShell 3.0에서 추가됐어요.

이 타입 액셀러레이터가 생기기 전에는, 멤버 속성과 값을 가진 객체를 만드는 게 꽤 번거로웠어요. 원래는 New-Object로 객체를 만들고 Add-Member로 속성을 붙여 줘야 했죠. 예를 들면 이렇게요:

PS> $object1 = New-Object -TypeName psobject
PS> Add-Member -InputObject $object1 -MemberType NoteProperty -Name one -Value 1
PS> Add-Member -InputObject $object1 -MemberType NoteProperty -Name two -Value 2
PS> $object1 | Get-Member

   TypeName: System.Management.Automation.PSCustomObject

Name        MemberType   Definition
----        ----------   ----------
Equals      Method       bool Equals(System.Object obj)
GetHashCode Method       int GetHashCode()
GetType     Method       type GetType()
ToString    Method       string ToString()
one         NoteProperty int one=1
two         NoteProperty int two=2

PS> $object1

one two
--- ---
  1   2

나중에는 New-ObjectProperty 파라미터로 멤버와 값을 담은 Hashtable을 넘기는 방식도 생겼어요.

PS> $object2 = New-Object -TypeName psobject -Property @{one=1; two=2}
PS> $object2 | Get-Member

   TypeName: System.Management.Automation.PSCustomObject

Name        MemberType   Definition
----        ----------   ----------
Equals      Method       bool Equals(System.Object obj)
GetHashCode Method       int GetHashCode()
GetType     Method       type GetType()
ToString    Method       string ToString()
one         NoteProperty int one=1
two         NoteProperty int two=2

PS> $object2

one two
--- ---
  1   2

PowerShell 3.0부터는 Hashtable을 [pscustomobject]로 캐스팅하면 같은 결과를 얻을 수 있어요.

PS> $object3 = [pscustomobject]@{one=1; two=2}
PS> $object3 | Get-Member

   TypeName: System.Management.Automation.PSCustomObject

Name        MemberType   Definition
----        ----------   ----------
Equals      Method       bool Equals(System.Object obj)
GetHashCode Method       int GetHashCode()
GetType     Method       type GetType()
ToString    Method       string ToString()
one         NoteProperty int one=1
two         NoteProperty int two=2

PS> $object3

one two
--- ---
  1   2

PSObject 타입 객체는 멤버가 객체에 추가된 순서대로 멤버 목록을 유지해요. Hashtable 객체는 키-값 쌍의 순서를 보장하지 않지만, 리터럴 Hashtable을 [pscustomobject]로 캐스팅하면 순서가 유지돼요.

여기서 짚고 넘어가야 할 게 하나 있어요. Hashtable은 리터럴이어야 해요. Hashtable을 괄호로 감싸거나, Hashtable을 담은 변수를 캐스팅하면 순서가 보존된다는 보장이 없어요.

$hash = @{
    Name      = "Server30"
    System    = "Server Core"
    PSVersion = "4.0"
}
$Asset = [pscustomobject]$hash
$Asset
System      Name     PSVersion
------      ----     ---------
Server Core Server30 4.0

타입 액셀러레이터 이해하기

[psobject][pscustomobject]는 타입 액셀러레이터예요.

자세한 내용은 about_Type_Accelerators 문서를 참고하세요.

[pscustomobject]System.Management.Automation.PSCustomObject에 매핑될 거라고 생각하기 쉬운데, 실제 타입은 달라요.

PS> [pscustomobject] -eq [System.Management.Automation.PSCustomObject]
False

두 타입 액셀러레이터 모두 PSObject라는 같은 클래스에 매핑돼요.

PS> [pscustomobject]

IsPublic IsSerial Name                                     BaseType
-------- -------- ----                                     --------
True     True     PSObject                                 System.Object

PS> [psobject]

IsPublic IsSerial Name                                     BaseType
-------- -------- ----                                     --------
True     True     PSObject                                 System.Object

[pscustomobject] 타입 액셀러레이터가 PowerShell에 추가될 때, Hashtable을 PSObject 타입으로 변환하는 특별한 코드가 함께 포함됐어요. 이 특별한 코드는 새 객체가 생성될 때만 호출돼요. 그래서 [pscustomobject]는 타입 강제 변환(type coercion)이나 타입 비교에는 쓸 수 없어요. 모든 객체가 PSObject 타입으로 취급되기 때문이죠.

예를 들어, -is 연산자로 어떤 cmdlet이 반환한 객체가 [pscustomobject]인지 확인하는 건, 그 객체를 [psobject]와 비교하는 것과 같아요.

PS> (Get-Item /) -is [pscustomobject]
True

PS> (Get-Item /) -is [psobject]
True

어떤 객체든 [psobject]로 캐스팅하면 원래 객체의 타입을 그대로 얻게 돼요. 그래서 Hashtable이 아닌 다른 것을 [pscustomobject]로 캐스팅해도 결과 타입은 같아요.

PS> ([psobject]@{Property = 'Value'}).GetType().FullName
System.Collections.Hashtable

PS> ([pscustomobject]123).GetType().Name
Int32

PS> ([pscustomobject]@{Property = 'Value'}).GetType().FullName
System.Management.Automation.PSCustomObject

객체를 [psobject]로 캐스팅하면 타입에는 아무 영향이 없는 것처럼 보이지만, 사실 PowerShell이 객체 주위에 보이지 않는 [psobject] 래퍼를 추가해요. 이 때문에 눈치채기 어려운 부작용이 생길 수 있어요.

  • 래핑된 객체는 원래 타입과 [psobject] 타입 둘 다에 일치해요.
PS> 1 -is [int32]
True
PS> 1 -is [psobject]
False
PS> ([psobject] 1) -is [int32]
True
PS> ([psobject] 1) -is [psobject]
True
  • 서식 연산자(-f)는 [psobject]로 래핑된 배열을 인식하지 못해요.
PS> '{0} {1}' -f (1, 2)
1 2
PS> '{0} {1}' -f ([psobject] (1, 2))
Error formatting a string: Index (zero based) must be greater than or equal
to zero and less than the size of the argument list..

대소문자만 다른 키를 가진 Hashtable 변환

대소문자를 구분하는 사전(dictionary)에는 대소문자만 다른 키 이름이 들어 있을 수 있어요. 그런 사전을 [pscustomobject]로 캐스팅하면 PowerShell은 키의 대소문자를 보존하지만, 대소문자 자체는 구분하지 않아요. 그래서 이런 결과가 나와요:

  • 첫 번째 중복 키의 대소문자가 그 키의 이름이 돼요.
  • 마지막 대소문자 변형 키의 값이 그 속성의 값이 돼요.

다음 예시가 이 동작을 보여줘요.

$Json = '{
  "One": 1,
  "two": 2,
  "Two": 3,
  "three": 3,
  "Three": 4,
  "THREE": 5
}'
$OrderedHashTable = $Json | ConvertFrom-Json -AsHashTable
$OrderedHashTable

정렬된 Hashtable에 대소문자만 다른 키가 여러 개 들어 있다는 점을 눈여겨보세요.

Name                           Value
----                           -----
One                            1
two                            2
Two                            3
three                          3
Three                          4
THREE                          5

그 Hashtable을 [pscustomobject]로 캐스팅하면, 첫 번째 키 이름의 대소문자가 사용되고, 마지막으로 일치하는 키 이름의 값이 사용돼요.

[pscustomobject]$OrderedHashTable
One two three
--- --- -----
  1   3     5

참고 사항

Windows PowerShell에서 Hashtable을 [pscustomobject]로 캐스팅해 만든 객체에는 LengthCount 속성이 없어요. 이런 멤버에 접근하려 하면 $null을 반환해요.

예를 들면:

PS> $object = [pscustomobject]@{key = 'value'}
PS> $object

key
---
value

PS> $object.Count
PS> $object.Length

반면 PowerShell 6부터는 Hashtable을 [pscustomobject]로 캐스팅해 만든 객체가 LengthCount 속성에 항상 값 1을 가져요.

See also

원문의 See also 목록은 아래 링크와 같아요. 관련 주제들을 더 알아보고 싶다면 여기를 눌러 보세요.

더 알아보기