about_PSCustomObject
about_PSCustomObject
[psobject]와 [pscustomobject] 두 타입 액셀러레이터는 이름이 비슷해 보여서, 언뜻 같은 걸 가리키는 것처럼 느껴지기 쉬워요. 그런데 실제로는 역할이 조금씩 달라요. 이 문서에서는 두 타입 액셀러레이터가 어떻게 다르고, 언제 [pscustomobject]를 쓰는 게 좋은지 하나씩 짚어볼게요.
본문
[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-Object의 Property 파라미터로 멤버와 값을 담은 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]로 캐스팅해 만든 객체에는 Length나 Count 속성이 없어요. 이런 멤버에 접근하려 하면 $null을 반환해요.
예를 들면:
PS> $object = [pscustomobject]@{key = 'value'}
PS> $object
key
---
value
PS> $object.Count
PS> $object.Length
반면 PowerShell 6부터는 Hashtable을 [pscustomobject]로 캐스팅해 만든 객체가 Length와 Count 속성에 항상 값 1을 가져요.
See also
원문의 See also 목록은 아래 링크와 같아요. 관련 주제들을 더 알아보고 싶다면 여기를 눌러 보세요.