[ref]로 참조형 변수 다루기 — about_Ref

[ref]로 참조형 변수 다루기 — about_Ref

변수를 함수에 넘길 때, 값만 복사해서 넘길지 아니면 원본을 참조로 넘길지를 고른 적 있으신가요? 파워셸의 [ref]가 바로 그 두 번째 길을 열어주는 도구예요. 이 글에서는 참조형 변수를 만들고, 함수나 .NET 메서드에 참조로 넘기는 방법을 하나씩 따라가 볼게요.

출처: https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_ref

본문

참조형 변수란

변수는 함수에 값으로 넘길 수도 있고, 참조로 넘길 수도 있어요. 값으로 넘기면 데이터의 복사본이 전달되지만, 참조로 넘기면 원본 값을 가리키는 참조가 전달돼요. 덕분에 함수가 넘겨받은 변수의 값을 직접 바꿀 수 있게 되죠. 참조형 변수는 [ref]로 만드는데, [ref][System.Management.Automation.PSReference] 타입의 형식 가속기(type accelerator)예요.

[ref]의 주된 용도는 파워셸 변수를 ref, out, in으로 표시된 .NET 메서드 파라미터에 참조로 넘기는 거예요. 직접 [ref] 타입 파라미터를 받는 파워셸 함수를 만들 수도 있고요. 이 경우 [ref]를 변수에 적용하면, 그 결과로 만들어진 참조 인스턴스를 통해 변수 값을 간접적으로 바꿀 수 있게 돼요.

아래 예시를 보면 함수가 넘겨받은 값 바꾸려고 해요. 파워셸에서 정수는 값 타입이라 값으로 전달되죠. 그래서 함수 밖에서는 $var 값이 그대로 남아 있어요.

Function Test($Data)
{
    $Data = 3
}

$var = 10
Test -Data $var
$var
10

이번에는 Hashtable을 담은 변수를 함수에 넘겨볼게요. Hashtable은 객체 타입이라 기본적으로 참조로 전달돼요. 참조로 넘기면 함수가 데이터를 바꿔도 그 변경이 함수 실행이 끝난 뒤에도 유지돼요.

Function Test($Data)
{
    $Data.Test = "New Text"
}

$var = @{}
Test -Data $var
$var
Name                           Value
----                           -----
Test                           New Text

이 함수는 함수 스코프 밖에서도 남는 키-값 쌍을 하나 새로 추가했죠.

참조 파라미터를 받는 함수 만들기

함수를 정의할 때, 어떤 종류의 데이터가 오든 그 파라미터를 참조로 받도록 만들 수 있어요. 그러려면 파라미터 타입을 [ref]로 지정해 주면 돼요. 참조를 쓸 때는 [ref] 타입의 Value 속성으로 데이터에 접근해야 해요.

function Test {
    param([ref]$Data)
    $Data.Value = 3
}

참조를 기대하는 파라미터에 변수를 넘기려면, 그 변수를 참조로 형변환(type cast)해야 해요.

중요: 대괄호와 괄호는 둘 다 있어야 해요.

$var = 10
Test -Data ([ref]$var)
$var
3

.NET 메서드에 참조 넘기기

일부 .NET 메서드는 변수를 참조로 넘겨야 할 때가 있어요. 메서드 정의가 파라미터에 in, out, ref 같은 키워드를 쓰면, 그 파라미터는 참조를 기대하는 거예요.

[int] | Get-Member -Static -Name TryParse
Name     MemberType Definition
----     ---------- ----------
TryParse Method     static bool TryParse(string s, [ref] int result)

TryParse 메서드는 문자열을 정수로 파싱하려고 시도해요. 파싱에 성공하면 $true를 돌려주고, 그 결과는 참조로 넘긴 변수에 저장돼요.

PS> $number = 0
PS> [int]::TryParse("15", ([ref]$number))
True
PS> $number
15

참조와 스코프

참조를 쓰면 부모 스코프에 있는 변수의 값을 자식 스코프 안에서 바꿀 수 있어요.

# Create a value type variable.
$i = 0
# Create a reference type variable.
$iRef = [ref]0
# Invoke a scriptblock to attempt to change both values.
&{$i++;$iRef.Value++}
# Output the results.
"`$i = $i;`$iRef = $($iRef.Value)"
$i = 0;$iRef = 1

바뀐 건 참조형 변수뿐이었죠.

일반적인 객체 보관소로 [ref] 쓰기

[ref]를 일반적인 객체 보관소(holder)로도 쓸 수 있어요. 이 경우엔 변수가 아니라 값에 [ref]를 적용해요. 보통 그 값은 숫자 같은 값 타입의 인스턴스예요. 대부분의 상황에서는 일반 변수나 파라미터를 쓰면 되는데, 명시적인 값 보관소를 넘기는 게 번거롭거나 불가능한 경우엔 이 기법이 유용해요. 예를 들어 스크립트 블록 파라미터 값 같은 경우가 그렇죠.

Rename-Item cmdlet의 NewName 파라미터 값을 계산할 때 스크립트 블록 파라미터 값을 쓸 수 있어요. Rename-Item은 파이프라인으로 항목을 받을 수 있죠. 이 cmdlet은 파이프라인의 각 항목마다 NewName에 넘어온 스크립트 블록을 실행해요. 이 스크립트 블록은 자식 스코프에서 실행되는데, 호출자 스코프의 변수를 직접 바꿔봐야 소용없고 이 상황에서는 스크립트 블록에 인자를 넘길 수도 없어요.

아래 예시에서 NewName 파라미터에 넘어온 스크립트 블록은 파이프라인의 각 항목마다 $iRef 값을 하나씩 올려요. 이 스크립트 블록은 파일 이름 앞에 숫자를 붙여 새 이름을 만들어내요.

$iRef = [ref] 0
Get-ChildItem -File $setPath |
    Rename-Item -NewName { '{0} - {1}' -f $iRef.Value++,$_.Name }

[ref][System.Management.Automation.PSReference]의 차이

참조형 변수는 [ref] 형식 가속기로 만들 수도 있고, [System.Management.Automation.PSReference] 타입을 직접 지정해서 만들 수도 있어요. 그런데 [ref][System.Management.Automation.PSReference]의 형식 가속기임에도, 둘은 다르게 동작해요.

  • [ref]로 변수를 형변환하면, 파워셸은 변수의 원본 인스턴스에 대한 참조를 담은 참조 객체를 만들어요.
  • [System.Management.Automation.PSReference]로 변수를 형변환하면, 파워셸은 원본 인스턴스에 대한 참조가 아니라 변수의 복사본을 담은 참조 객체를 만들어요.

예를 들어 아래 스크립트는 $x 변수와 참조 객체 두 개를 만들어요.

PS> $int = 1
PS> $aRef = [ref] $int
PS> $bRef = [System.Management.Automation.PSReference] $int
PS> $int
1
PS> $aRef, $bRef

Value
-----
    1
    1

이 시점에서 두 참조 객체는 $int와 같은 값을 가져요. 참조 객체들에 서로 다른 값을 더하면, [ref]로 만든 $aRef$int의 원본 인스턴스를 참조한다는 걸 알 수 있어요. 반면 [System.Management.Automation.PSReference]로 만든 $bRef는 변수의 복사본이에요.

PS> $aRef.Value+=2
PS> $bRef.Value+=5
PS> $int
3
PS> $aRef, $bRef

Value
-----
    3
    6

더 알아보기