PowerShell Hashtable和数组的特殊问题

时间:2015-06-04 13:10:16

标签: powershell

我今天正在研究一些事情,在测试过程中我发现了一个非常特殊的问题

$arry = @()
$Msg = @{Body="This is a Sample Message";}

$Msg.BrokerProperties=@{}
$Msg.BrokerProperties.Label= "Msg1"

$arry += $Msg
$arry | ConvertTo-Json    # 1st Result

$Msg.BrokerProperties=@{}
$Msg.BrokerProperties.Label= "Msg2"

$arry += $Msg

$arry | ConvertTo-Json

$arry | ConvertTo-Json的第一个结果如下

  

{       “身体”:“这是一个示例消息”,       “BrokerProperties”:{                                “标签”:“Msg1”                            }}

$arry | ConvertTo-Json的第二个结果如下

  

[       {           “身体”:“这是一个示例消息”,           “BrokerProperties”:{                                    “标签”:“Msg2”                                }       },       {           “身体”:“这是一个示例消息”,           “BrokerProperties”:{                                    “标签”:“Msg2”                                }       }]

我认为会发生的事情是当我第二次设置$Msg.BrokerProperties.Label= "Msg2"时,它只会影响数组中的第二个哈希表。但非常有趣的是,该属性甚至被注入到第一个哈希表中。

有人可以解释一下这种行为吗?

我实际上是在循环中执行此操作以准备发送到API调用的JSON有效负载,因此我期待一种方法来更新json对象中每个内部的标签

2 个答案:

答案 0 :(得分:3)

显然,如果向其添加变量,Powershell数组只保存对象指针,因此您只需将对单个对象的两个引用添加到arry中。为了澄清,在你的操作之后这句话:

$arry[0] -eq $arry[1]

将返回true。要解决此问题,您应该使用clone()函数从$Msg创建一个全新的独立对象,这样任何修改都不会改变您存储在数组中的对象。

$arry = @()
$Msg = @{Body="This is a Sample Message";}

$Msg.BrokerProperties=@{}
$Msg.BrokerProperties.Label= "Msg1"

$arry += $Msg
$arry | ConvertTo-Json    # 1st Result

$Msg=$Msg.clone() # create a new copy
$Msg.BrokerProperties.Label= "Msg2" # alter new copy

$arry += $Msg

$arry | ConvertTo-Json # get two different tables in array

编辑:在您的情况下,您还必须克隆BrokerProperties,因为它也是哈希表,克隆上层$Msg会导致两个不同的对象其中包含指向单个嵌套哈希表的链接。因此,为了接收完全不同的对象,您必须执行哈希表的deep copy

$arry = @()
$Msg = @{Body="This is a Sample Message";}

$Msg.BrokerProperties=@{}
$Msg.BrokerProperties.Label= "Msg1"

$arry += $Msg
$arry | ConvertTo-Json    # 1st Result

# $Msg=$Msg.clone() this is not enough!!!
$memStream = new-object IO.MemoryStream
$formatter = new-object Runtime.Serialization.Formatters.Binary.BinaryFormatter
$formatter.Serialize($memStream,$Msg) # serialization makes a string out of an object's structure
$memStream.Position=0
$Msg = $formatter.Deserialize($memStream) # deserialization makes a completely different object
$Msg.BrokerProperties.Label= "Msg2" # and now changing the nested hash table won't change the old one.

$arry += $Msg

$arry | ConvertTo-Json # get two different tables in array

旁注:如果要基于相同的"模板创建多个对象"对象,您不需要一直序列化,只需保留对$memStream$formatter的引用(每个对象一个内存流进行深层复制,每个脚本使用一个格式化程序),然后调用{ {1}}接收先前序列化的同一对象的另一个准备副本。

答案 1 :(得分:2)

Hashtables通过引用而不是按值传递

要从现有哈希表创建新哈希表,请使用Clone()

$arry += [hashtable]$Msg.Clone()

请注意,这会创建一个浅层克隆,所以如果你有嵌套的哈希表,那么最里面的条目仍然是引用类型,并且根据具体情况,你可能想要编写自己的克隆函数