使用数组作为哈希表中的键

lit*_*lit 7 powershell

数组可以用作哈希表中的键吗?如何使用数组键引用哈希表项?

PS C:\> $h = @{}
PS C:\> $h[@(1,2)] = 'a'
PS C:\> $h

Name                           Value
----                           -----
{1, 2}                         a         # looks like the key is a hash

PS C:\> $h[@(1,2)]                       # no hash entry
PS C:\> $h.Keys                          # 
1
2
PS C:\> $h[@(1,2)] -eq 'a'
PS C:\> $h[@(1,2)] -eq 'b'
PS C:\> foreach ($key in $h.Keys) { $key.GetType() }   # this is promising

IsPublic IsSerial Name                                     BaseType
-------- -------- ----                                     --------
True     True     Object[]                                 System.Array

PS C:\> $PSVersionTable.PSVersion.ToString()
7.1.4
Run Code Online (Sandbox Code Playgroud)

mkl*_*nt0 7

虽然您可以使用数组作为哈希表键,但这样做是不切实际的

  • 更新:有一种方法可以使数组用作哈希表键,但在构造哈希表期间需要付出很大的努力 - 请参阅此答案

  • 您将使用完全相同的数组实例作为键并用于以后的查找。

    • 原因是数组是.NET引用类型的实例(与整数等值类型相反),使用该方法的默认实现.GetHashCode()来返回哈希码(如哈希表中使用的那样),并且此默认实现返回一个每个实例都有不同的代码- 即使对于人们直观地认为“相同”的两个数组实例也是如此。
    • 换句话说:尝试使用任何此类 .NET 引用类型的实例作为哈希表键(包括其他集合类型)时,您将遇到相同的问题- 除非给定类型恰好有一个自定义.GetHashCode()实现,该实现显式地认为不同实例相等他们的内容。
  • 此外,它使使用 PowerShell 的索引器语法 ( [...]) 变得尴尬,因为数组实例必须使用数组构造函数运算符的一元形式进行嵌套。但是,点符号(属性访问)照常工作。,

$h = @{}

# The array-valued key.
$key = 1, 2

$h[$key] = 'a'

# IMPORTANT:
# The following lookups work, but only because
# the *very same array instance* is used for the lookup.

# Nesting required so that PowerShell doesn't think that
# *multiple* keys are being looked up.
$h[, $key] 

# Dot notation works normally.
$h.$key

# Does NOT work, because a *different array instance* is used.
$h.@(1,2)
Run Code Online (Sandbox Code Playgroud)

测试给定表达式是否每次都会产生相同的哈希表查找结果并因此适合作为键的简单测试是重复调用其.GetHashCode()方法;仅当每次(在给定会话中)返回相同的数字时才可以使用表达式:

# Returns *different* numbers.
@(1, 2).GetHashCode()
@(1, 2).GetHashCode()
Run Code Online (Sandbox Code Playgroud)

检查给定对象或类型是否是.NET引用类型(的实例)还是类型

# $false is returned in both cases, confirming that the .NET array 
# type is a *reference type*
@(1, 2).GetType().IsValueType
[Array].IsValueType
Run Code Online (Sandbox Code Playgroud)

解决方法:

解决方法是使用数组的字符串表示形式,尽管提出唯一(足够)的数组可能是一个挑战

最简单的情况下,使用PowerShell 的字符串插值,它将数组表示为以空格分隔的元素(字符串化)值列表;例如"$(1, 2)"逐字产生1 2

$h = @{}

# The array to base the key on.
$array = 1, 2

# Use the *stringified* version as the key.
$h["$array"] = 'a'

# Works, because even different array instances with equal-valued
# instances of .NET primitive types stringify the same.
#   '1 2'
$h["$(1, 2)"]
Run Code Online (Sandbox Code Playgroud)

iRon指出,这种简单化的方法可能会导致歧义(例如,单个'1 2'字符串会产生与 array 相同的键1, 2),并建议采用以下方法:

数组键的更高级/显式方法是:

  • 用不可打印的字符连接它们的元素;例如
    $key = $array -join [char]27
  • 或者,对于复杂对象数组元素序列化数组:
    $key = [System.Management.Automation.PSSerializer]::Serialize($array)

请注意,即使类提供的基于 XML(字符串)的序列化System.Management.Automation.PSSerializer(在 PowerShell 远程处理和后台作业中用于跨进程封送处理)在可靠区分实例方面也有其局限性,因为它的递归深度是有限的 - 请参阅此答案更多信息; 您可以根据需要增加深度,但这样做可能会导致非常大的字符串表示形式。

一个具体的例子:

using namespace System.Management.Automation

$ht = @{}

# Use serialization on an array-valued key.
$ht[[PSSerializer]::Serialize(@(1, 2))] = 'a'

# Despite using a different array instance, this
# lookup succeeds, because the serialized representation is the same.
$ht[[PSSerializer]::Serialize(@(1, 2))]  # -> 'a'
Run Code Online (Sandbox Code Playgroud)