Создание ссылки на элемент хеш-таблицы - PullRequest
0 голосов
/ 02 мая 2018

Итак, я пытаюсь создать переменную в виде дерева, которую я мог бы использовать для навигации по данным. Я столкнулся с проблемой при попытке использовать ссылочные переменные в хеш-таблицах в PowerShell. Рассмотрим следующий код:

$Tree = @{ TextValue = "main"; Children = @() }
$Item = @{ TextValue = "sub"; Children = @() }
$Pointer = [ref] $Tree.Children
$Pointer.Value += $Item
$Tree

При проверке ссылочной переменной $Pointer отображаются соответствующие значения, но основная переменная $Tree не затрагивается. Нет ли способа создать ссылки на элемент хеш-таблицы в PowerShell, и мне придется переключиться на двумерный массив?

Изменить с более подробной информацией:

Я принял ответ Матиаса, поскольку использование List выглядит именно так, как мне нужно, но требуется немного больше ясности относительно того, как взаимодействуют массивы и ссылки. Попробуйте этот код:

$Tree1 = @()
$Pointer = $Tree1
$Pointer += 1
Write-Host "tree1 is " $Tree1
$Tree2 = @()
$Pointer = [ref] $Tree2
$Pointer.Value += 1
Write-Host "tree2 is " $Tree2

Как видно из выходных данных, можно получить ссылку на массив, а затем изменить размер массива с помощью этой ссылки. Я думал, что это также будет работать, если массив является элементом другого массива или хэш-таблицы, но это не так. Кажется, PowerShell справляется с этим по-разному.

Ответы [ 2 ]

0 голосов
/ 02 мая 2018

К дополнить полезный ответ Матиаса Р. Йессена :

  • Действительно, любой массив имеет фиксированный размер и не может быть расширен вместо (@() создает пустой [object[]] массив).

  • += в PowerShell незаметно создает новый массив , с копией всех исходных элементов плюс новый (ие), и назначает что на LHS.


Использование [ref] бессмысленно , поскольку одного $Pointer = $Tree.Children достаточно для копирования ссылки в массив, хранящийся в $Tree.Children.

См. Нижний раздел для обсуждения надлежащего использования [ref].

Таким образом, и $Tree.Children и $Pointer будут содержать ссылку на один и тот же массив, как это делает $Pointer.Value в подходе, основанном на [ref].

Поскольку += создает новый массив, однако, что бы ни было на LHS - будь то $Pointer.Value или без [ref], просто $Pointer - просто получает новое ссылка на массив new , тогда как $Tree.Children все еще указывает на старый.

Вы можете проверить это, используя прямой способ определить, указывают ли две переменные или выражения на один и тот же экземпляр ссылочного типа (то есть все коллекции):

PS> [object]::ReferenceEquals($Pointer.Value, $Tree.Children)
False

Обратите внимание, что [object]::ReferenceEquals() применимо только к ссылочным типам , а не типам значений - переменные, содержащие последние значения хранилища напрямую вместо ссылки данные хранятся в другом месте .


Подход Матиаса решает вашу проблему, используя экземпляр [List`1] вместо массива, который может быть расширен на месте его методом .Add(), так что ссылка сохраненный в $Pointer[.Value] никогда не нуждается в изменении и продолжает ссылаться на тот же список, что и $Tree.Children.


Относительно вашего последующего вопроса: соответствующее использование [ref]:

$Tree2 = @()
$Pointer = [ref] $Tree2

В этом случае , поскольку [ref] применяется к переменной - как и было задумано - создает эффективный псевдоним переменной : $Pointer.Value сохраняет , указывая на то, что содержит $Tree2, даже если для $Tree2 позже назначены разные данные (независимо от того, являются ли эти данные экземпляром типа значения или ссылочного типа):

PS> $Tree2 = 'Now I am a string.'; $Pointer.Value
Now I am a string.

Также обратите внимание, что более типичный вариант использования [ref] - передача переменных в функции в виде по ссылке параметров.

PS> function foo { param([ref] $vRef) ++$vRef.Value }; $v=1; foo ([ref] $v); $v
2  # value of $v was incremented via $vRef.Value

В отличие от этого вы не можете использовать [ref] для создания такой постоянной косвенной ссылки на данные , такой как свойство объекта , содержащего в переменной, и использование [ref] там по существу бессмысленно:

$Tree2 = @{ prop = 'initial val' }
$Pointer = [ref] $Tree2.prop   # [ref] is pointless here

Позднее изменение $Tree2.prop означает , а не , отраженное в $Pointer.Value, поскольку $Pointer.Value статически относится к ссылке , первоначально , хранящейся в $Tree2.prop:

PS> $Tree2.prop = 'later val'; $Pointer.Value
initial val   # $Pointer.Value still points to the *original* data

PowerShell должен, вероятно, предотвращать использование [ref] с чем-либо, что не является переменной - см. эту проблему GitHub .

0 голосов
/ 02 мая 2018

Я подозреваю, что это неблагоприятный побочный эффект от того, как += работает с массивами.

Когда вы используете += для массива фиксированного размера, PowerShell заменяет исходный массив новым (и более крупным) массивом. Мы можем проверить, что $Pointer.Value больше не ссылается на тот же массив с GetHashCode():

PS C:\> $Tree = @{ Children = @() }
PS C:\> $Pointer = [ref]$Tree.Children
PS C:\> $Tree.Children.GetHashCode() -eq $Pointer.Value.GetHashCode()
True
PS C:\> $Pointer.Value += "Anything"
PS C:\> $Tree.Children.GetHashCode() -eq $Pointer.Value.GetHashCode()
False

Один из способов добиться этого - избегать использования @() и +=.

Вместо этого вы можете использовать List тип:

$Tree = @{ TextValue = "main"; Children = New-Object System.Collections.Generic.List[psobject] }
$Item = @{ TextValue = "sub"; Children = New-Object System.Collections.Generic.List[psobject] }
$Pointer = [ref] $Tree.Children
$Pointer.Value.Add($Item)
$Tree
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...