具有意外结果的 HashTable 枚举

Mat*_*ald 3 powershell enumeration hashtable enumerator

这可能是一个愚蠢的问题,所以请友善,哈哈。我正试图集中精力思考我刚刚遇到的事情。

第一的

我有一个可以使用以下内容枚举的哈希表:

$ht = [hashtable]@{"Key1"="Value1";"Key2"="Value2"}
$ht.GetEnumerator()

Name                           Value
----                           -----
Key1                           Value1
Key2                           Value2
Run Code Online (Sandbox Code Playgroud)

如果我将其存储到一个变量中,则它仅在该变量的一次调用中存在。

$KeyPairs = $ht.GetEnumerator()
$KeyPairs

Name                           Value
----                           -----
Key1                           Value1
Key2                           Value2

$KeyPairs
# Nothing is returned as if $KeyPairs lost its value
Run Code Online (Sandbox Code Playgroud)

有人可以帮助我理解为什么会这样吗?

第二

通常,对于集合,我有时想要针对单个项目进行测试(例如查看一个实例的属性),我可以使用 Select-Object 或通过索引:

$array = @("Value1","Value2")
$array | select -first 1
Value1

$array[0]
Value1
Run Code Online (Sandbox Code Playgroud)

哈希表枚举器似乎只支持 Select-Option,而不支持索引。

$ht = [hashtable]@{"Key1"="Value1";"Key2"="Value2"}
$ht.GetEnumerator() | Select -first 1
$ht.GetEnumerator() | Select -first 1

Name                           Value
----                           -----
Key1                           Value1


($ht.GetEnumerator() | measure).count
2

($ht.GetEnumerator())[0]

Name                           Value
----                           -----
Key1                           Value1
Key2                           Value2
Run Code Online (Sandbox Code Playgroud)

有人也可以解释一下吗?为什么我不能在这里使用索引选项?$ht.GetEnumerator() 返回一个 Dictionary 集合,当使用 Select -first 1 时,它返回单个 DictionaryEntry,但是当引用索引时,它返回两个字典条目。

mkl*_*nt0 6

注意:这个答案同样适用于 API直接返回枚举器对象的情况。

using.GetEnumerator()返回键值对的枚举器[1] ,与枚举的结果不同

  • 必须通过其方法迭代枚举才能执行实际的枚举。但是,您可以让 PowerShell 为您完成此操作,如下所示。.MoveNext()

要获得所需的行为,请通过数组子表达式运算符强制枚举@(...),并使用结果

# Note the use of @(...), which collects the enumerated objects
# in an [object[]] array.

# Get an array of key-value pairs.
$KeyPairs = @($ht.GetEnumerator())

# Get the first key-value pair.
@($ht.GetEnumerator())[0]
Run Code Online (Sandbox Code Playgroud)

笔记:

  • 在管道中, PowerShell 本身执行枚举器对象的枚举,这就是为什么类似的东西
    $ht.GetEnumerator() | ForEach-Object { <# work with each key-value pair #> }可以工作。

  • 在管道中,哈希表/字典在技术上也是集合,与数组等类似列表的集合不同,默认情况下不会枚举它们。[2]也就是说,默认情况下,哈希表/字典作为一个整体通过管道发送,这就是为什么需要调用来返回其 条目(键值对)的枚举器,然后管道将枚举该枚举器。.GetEnumerator()


至于你尝试过的

$KeyPairs
# Nothing is returned as if $KeyPairs lost its value
Run Code Online (Sandbox Code Playgroud)

因为$KeyPairs包含一个enumerator,所以它是在隐式执行的输出到管道(显示)的第一个枚举之后完成枚举的,因此在重新调用时没有什么可枚举的- 除非您先调用。 但是,请注意,并非每个枚举器都保证支持重复枚举- 某些枚举器总是执行一次性枚举。$KeyPairs.Reset()
.Reset()

($ht.GetEnumerator())[0] # !! DOESN'T WORK
Run Code Online (Sandbox Code Playgroud)
  • 无法对枚举建立索引。

  • PowerShell 将其视为单个对象(确实如此)并回退到其自己的索引,其中甚至允许对单个对象(标量)进行索引,以便统一处理集合和标量;在这种情况下,[0]是一个有效的no-op,只需返回单个对象本身(类似于(42)[0]和)(42)[-1]42


[1] 具体来说,.GetEnumerator()返回一个实现该System.Collections.IDictionaryEnumerator接口的对象。

[2] 请参阅此答案的底部部分,了解 PowerShell 在管道中自动枚举哪些类型和不自动枚举哪些类型。


San*_*zon 5

$KeyPairs在你的例子中$KeyPairs = $ht.GetEnumerator()是一个误导性的名称,你没有在该变量中分配键/值对,你拥有的是一个类型的对象,一个实现接口的HashtableEnumerator类型,这个接口基本上是一个“合同”,指定如何哈希表可以而且应该被枚举。实现的细节之一是实例可以被枚举一次,如果你想再次枚举它,你必须调用它的方法。据我所知,这适用于实现接口的任何类型( 的基本接口)。IDictionaryEnumerator.Reset()IEnumeratorIDictionaryEnumerator

PS ..\pwsh> $ht = @{'Key1' = 'Value1'; 'Key2' = 'Value2' }
PS ..\pwsh> $enum = $ht.GetEnumerator()
PS ..\pwsh> $enum

Name                           Value
----                           -----
Key1                           Value1
Key2                           Value2

PS ..\pwsh> $enum.Reset()
PS ..\pwsh> $enum

Name                           Value
----                           -----
Key1                           Value1
Key2                           Value2
Run Code Online (Sandbox Code Playgroud)

至于第二个问题:

哈希表枚举器似乎只支持Select-Option,而不支持索引。

这是正确的,枚举器不实现IList接口,也不实现索引器

如果要使用索引,则必须将其转换为usingHashtableEnumerator的集合,在这种情况下,首先使用哈希表就没有意义,它将失去其目的DictionaryEntry@($ht.GetEnumerator())

或者,更好的方法是使用OrderedDictionary,这种类型支持按索引和按键访问,并且在执行查找时与普通哈希表一样快:

$dict = [ordered]@{'Key1' = 'Value1'; 'Key2' = 'Value2' }
$dict[0]      # Value1
$dict.Keys[0] # Key1

# or, for a key / value pair:
[System.Collections.DictionaryEntry]::new($dict.Keys[0], $dict[0])

# Name                           Value
# ----                           -----
# Key1                           Value1
Run Code Online (Sandbox Code Playgroud)