如果 属性 名称是整数,为什么我不能点引用哈希表键的值 属性

Why can't I dot-reference a hashtable key's value property if the property name is an integer

考虑以下哈希表:

$table = @{
  6           = '10.11.12.13', '10.11.12.14'
  15          = 'domain.tld'
  NameServers = '10.11.12.13', '10.11.12.14'
}

通常当你有一个哈希表时,你可以 . 引用键名以更面向对象的方式使用它而不是使用数组访问器语法 [key]。例如,使用 . 调用上面哈希表中的 NameServers 键会返回值,我可以按预期使用 属性 值的成员:

$table.NameServers       # ========> 10.11.12.13
                         # ========> 10.11.12.14

$table.NameServers.Count # ========> 2

但是,如果我尝试使用包含相同字符串内容的 . 访问 6 键,它会引用正确的值,但我无法在返回的对象上调用任何成员。我必须在这里使用传统的数组访问器:

$table.6        # =====> 10.11.12.13
                # =====> 10.11.12.14

$table.6.Count  # =====> ParserError:
                # =====> Line |
                # =====>    1 |  $hi.6.Count
                # =====>      |      ~
                # =====>      | Missing property name after reference operator.

$table[6].Count # =====> 2

当然,( $table.6 ).Count 解决了这个问题,但这仍然是我无法解释的语法怪癖。有趣的是,将哈希表转换为 PSCustomObject 会产生相同的问题。这可能是解析器错误吗?还是这里发生了其他事情?

我在 Windows PowerShell 和 PowerShell 7.1 中对此进行了测试,它在两者中都发生了。

我认为这是您问题的官方问题:

https://github.com/PowerShell/PowerShell/issues/14036

我认为最好的解决方法是 $table.(6).count

powershell 中的

Hashtables 期望键名是字符串。当您根据原始代码键入 $table.6 时,当它期望它是一个字符串(一种类型转换功能)时,6 被解释为一个整数。解决此问题的最简单方法是将 6 替换为 'six',但是您可以绕过此问题。

如果您像这样更改代码:

$table = @{
  '6' = '10.11.12.13', '10.11.12.14'
  '15' = 'domain.tld'
  NameServers = '10.11.12.13', '10.11.12.14'
}

您可以通过执行以下操作隐式指示 powershell 将 6 视为字符串来引用它:

$table.'6'.count
2

这与添加特殊字符的键名的原理相同,它们必须被实例化为字符串以防止插值或类型转换。例如:

$table = @{'bla $something' = 'anything'}
$table.'bla $something'
anything

对于你的情况,我将执行以下操作并继续处理下一个问题:

$table = @{
  Six = '10.11.12.13', '10.11.12.14'
  Fifteen = 'domain.tld'
  NameServers = '10.11.12.13', '10.11.12.14'
}

$table.Six.count
2

要进一步研究类型转换,请遵循此 link:Powershell Type Conversion Reference

注意:这个答案最初错误地声称 $table.6 不起作用,因为哈希表键是 整数 类型,但它工作正常,因为 .6“属性”访问中的 6 也被解析为整数。

作为 points out, you're seeing problematic behavior in PowerShell's parser, present as of PowerShell 7.2, discussed in detail in GitHub issue #14036. The behavior is unrelated to hashtables 本身:

  • 成员访问运算符 . 之后的内容如果看起来像 数字,则被解析为 数字 文字,比如66l(一个[long])甚至6.0(!,一个[double]).

    • 请参阅下文了解该号码是如何使用
  • 任何尝试使用 另一个 属性 访问都会触发您看到的错误:

    $foo.6.bar # !! Error "Missing property name after reference operator."
    
    • 事实上,在 属性 之后 以数字 字母 开头的名称会导致错误也是如此 - 尽管 6a 可以 而不是 解释为数字文字。

      $foo.6a # !! Same error.
      

顺便说一句:PowerShell 甚至允许您使用 变量 引用,甚至 表达式 (包含在 (...) 中)作为 属性 名称(例如,$propName = 'Length'; 'foo'.$propName 或('foo'.('L' + 'ength')

解决方法

  • 正如您所展示的,在 (...) - ($table.6).Count 中包含第一个 属性 访问有效,$table.(6).Count[=62 也是如此=]

  • 您还演示了 $table[6].Count,它适用于 哈希表

  • 为了访问对象的实际 属性$obj.'6'.Count 也可以(假定 属性 名称始终strings),就像带有 string 键的哈希表一样(例如 @{ '6'= 'six' }.'6'.Length


特定于 哈希表的注意事项

作为语法上的便利,PowerShell允许属性访问语法($obj.someProperty) 也用于访问哈希表 条目 ,在这种情况下,属性“名称”被视为 哈希表中条目的键

访问哈希表条目的类型原生语法是索引语法 ($hash[$someKey]).

虽然 属性 names(指的是 .NET 类型的 members)总是 strings :

  • 哈希表的可以是任何类型.
  • 访问一个条目时,查找键不仅必须有正确的 , 但也必须是 与哈希表 .
  • 中存储的密钥完全相同的类型

也许令人惊讶的是,定义一个hashtable未引用 成为 stringsnumbers:

  • 如果未加引号的单词 可以 解释为 数字文字 (例如,6) ,它被这样解析,你最终得到一个 numeric 键;在 6 的情况下,它将是 [int] 类型的键,因为通常的数字文字类型适用(例如,1.0 将成为 [double] 键(不可取,因为 binary 浮点值并不总是具有精确的 decimal 表示),并且 2147483648 将成为 [long] ).

  • 否则(例如NameServers),密钥是[string]类型的。

    • 警告:如果名称 以数字 开头(但不是数字文字;例如 6a),错误 发生;使用引号 ('6a') 作为解决方法 - 请参阅 GitHub issue #15925

也就是说,即使表达式中的字符串通常需要 quoting,为方便起见,您可以在哈希表文字中定义键时省略引号 - 但识别规则 数字文字 仍然适用。

显式键入 哈希表键:

  • 为了确保给定的键被解释为[string]引用 它(例如,'6'

  • 通常,您也可以使用 cast 来显式键入您的密钥。 (例如[long] 6)或者,在数字文字中,通常的数字类型后缀字符,例如L代表[long](例如6L) - 请参阅 了解受支持后缀的概述,其数量在 PowerShell (Core) 7+ 中显着增加。

示例,基于您的哈希表:

从上面可以看出,您的 6 键是 [int] 类型(如果您希望它是字符串).

因为 $name.6 中的 6 解析为 [int],查找成功,但请注意它不会如果 不同的数字类型 在起作用则成功:

# !! Output is $null, because the entry key is of type [long], 
# !! whereas the lookup key is [int].
@{ 6L = '[long] 6' }.6

特定于 属性 访问的注意事项:

通过实际 属性 访问(访问 .NET 类型的本机成员),事实是 看起来 numbers 实际上被解析为数字 first - 之前,必须转换为 strings - 会导致令人惊讶的行为:

# !! Output is $null, because the 6L after "." is parsed as a 
# !! ([long]) number first, and then converted to a string, which
# !! results in "6" being used.
([pscustomobject] @{ '6L' = 'foo' }).6L

# OK - The quoting forces 6L to be a string.
([pscustomobject] @{ '6L' = 'foo' }).'6L' # -> 'foo'