没有 '.Count' 属性的对象 - 使用 @()(数组子表达式运算符)与 [Array] 转换 [英] Objects with no '.Count' Property - use of @() (array subexpression operator) vs. [Array] cast

查看:40
本文介绍了没有 '.Count' 属性的对象 - 使用 @()(数组子表达式运算符)与 [Array] 转换的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在尝试执行一些简单的 if 语句,但所有基于 [Microsoft.Management.Infrastructure.CimInstance] 的较新 cmdlet 似乎都没有公开 .count 方法?

I am trying to perform some simple if statements, but all of the newer cmdlets that are based upon [Microsoft.Management.Infrastructure.CimInstance] don't seem to expose a .count method?

$Disks = Get-Disk
$Disks.Count

不返回任何东西.我发现我可以将其转换为 [array],这使其按预期返回 .NET .count 方法.

Doesn't return anything. I found that I can cast this as an [array], which makes it returns a .NET .count method as expected.

[Array]$Disks = Get-Disk
$Disks.Count

无需直接将其转换为先前 cmdlet 的数组即可工作:

This works without directly casting it as an array for previous cmdlets:

(Get-Services).Count

解决这个问题的推荐方法是什么?

What is the recommended way to get around this?

一个不起作用的例子:

$PageDisk = Get-Disk | Where {($_.IsBoot -eq $False) -and ($_.IsSystem -eq $False)}
  If ($PageDisk.Count -lt 1) {Write-Host "No suitable drives."; Continue}
   Else If ($PageDisk.Count -gt 1) {Write-Host "Too many drives found, manually select it."}
   Else If ($PageDisk.Count -eq 1) { Do X }

选项 A(转换为数组):

Option A (Cast as Array):

[Array]$PageDisk = Get-Disk | Where {($_.IsBoot -eq $False) -and ($_.IsSystem -eq $False)}
  If ($PageDisk.Count -lt 1) {Write-Host "No suitable drives."; Continue}
   Else If ($PageDisk.Count -gt 1) {Write-Host "Too many drives found, manually select it."}
   Else If ($PageDisk.Count -eq 1) { Do X }

选项 B(使用数组索引):

Option B (Use Array Indexes):

 $PageDisk = Get-Disk | Where {($_.IsBoot -eq $False) -and ($_.IsSystem -eq $False)}
  If ($PageDisk[0] -eq $Null) {Write-Host "No suitable drives."; Continue}
   Else If ($PageDisk[1] -ne $Null) {Write-Host "Too many drives found, manually select it."}
   Else If (($PageDisk[0] -ne $Null) -and (PageDisk[1] -eq $Null)) { Do X }

选项 C(数组)-感谢@PetSerAl :

Option C (Array) -Thanks to @PetSerAl :

$PageDisk = @(Get-Disk | Where {($_.IsBoot -eq $False) -and ($_.IsSystem -eq $False)})
  If ($PageDisk.Count -lt 1) {Write-Host "No suitable drives."; Continue}
   Else If ($PageDisk.Count -gt 1) {Write-Host "Too many drives found, manually select it."}
   Else If ($PageDisk.Count -eq 1) { Do X }

基于 CIM 的 cmdlet 不公开 .Count 方法的原因是什么?处理此问题的推荐方法是什么?选项 B 对我来说似乎很复杂,而且难以阅读.选项 A 有效,但 powershell 不应该为我将其转换为数组吗?我是否以完全错误的方式处理这个问题?

What is the reason for CIM based cmdlets not exposing the .Count method? What is the recommended way to handle this? Option B seems convoluted to me, and hard to read. Option A works, but shouldn't powershell cast this as an array for me? Am I going about this in entirely the wrong way?

推荐答案

在 PSv3+ 中,凭借其对标量和集合的统一处理,任何 对象 - 甚至 $null - 应该有一个 .Count 属性(并且,除了 $null,应该支持用 [0]).

In PSv3+, with its unified handling of scalars and collections, any object - even $null - should have a .Count property (and, with the exception of $null, should support indexing with [0]).

任何支持上述的对象都应该被视为一个错误;例如:

Any occurrence of an object not supporting the above should be considered a bug; for instance:

  • 使用这个内在(引擎提供的) .Count 属性 意外失败Set-StrictMode-Version 2 或更高版本有效,即GitHub 问题 #2798 中报告的一个长期存在的错误,从 PowerShell 7.2 开始仍然存在(而 type-native .Count 属性,例如在数组上,可以安全地访问).

  • Using this intrinsic (engine-supplied) .Count property unexpectedly fails when Set-StrictMode-Version 2 or higher is in effect, which is a long-standing bug reported in GitHub issue #2798, still present as of PowerShell 7.2 (whereas a type-native .Count property, such as on an array, can safely be accessed).

([pscustomobject] 不遵守这些规则的实例是 已知错误已于 2017 年修复).

([pscustomobject] instances not playing by these rules was a known bug, fixed in 2017).

因为我不知道该错误是否与 [Microsoft.Management.Infrastructure.CimInstance#ROOT/Microsoft/Windows/Storage/MSFT_Disk] 实例有关,Get-Disk 输出,并且由于 Get-Disk - 至少目前 - 仅在 Windows PowerShell 中可用,我鼓励您在 uservoice.com.

Since I don't know if said bug is related to the [Microsoft.Management.Infrastructure.CimInstance#ROOT/Microsoft/Windows/Storage/MSFT_Disk] instances that Get-Disk outputs, and since Get-Disk - at least currently - is only available in Windows PowerShell, I encourage you to file a separate bug on uservoice.com.

使用数组子表达式运算符@(...)是必要的:

Use of array-subexpression operator @(...) is only necessary:

  • 作为手头错误/Set-StrictMode -Version 2 错误的解决方法.

  • as a workaround for the bug at hand / the Set-StrictMode -Version 2 bug.

万一标量对象碰巧有它自己的 .Count 属性.>

in case a scalar object happens to have its own .Count property.

通常,如果您确实需要确保某些东西是数组,请使用 @(...) 而不是 [Array] .../[object[]] ... - @() 是 PowerShell 惯用的,更简洁,语法更简单.

Generally, if you do need to ensure that something is an array, use @(...) rather than [Array] ... / [object[]] ... - @() is PowerShell-idiomatic, more concise, and syntactically easier.

也就是说,鉴于 @() 在技术上创建了现有数组的(浅)副本,您可能更喜欢 [Array] 在处理潜在的大型数组时.

That said, given that @() technically creates a (shallow) copy of an existing array, you may prefer [Array] when dealing with potentially large arrays.

此外,@(...)[Array] ...通常不等价,正如 PetSerAl 在对该问题的评论中所展示的有用示例;改编他的一个例子:

Additionally, @(...) and [Array] ... are not generally equivalent, as PetSerAl's helpful examples in a comment on the question demonstrate; to adapt one of his examples:

@($null) 返回一个元素为 $null 的单项数组,而 [Array] $null没有效果(保持 $null).

@($null) returns a single-item array whose one and only element is $null, whereas [Array] $null has no effect (stays $null).

@() 的这种行为与其目的一致(见下文):由于 $null 不是数组,@()> 将其包装成一个(产生一个 [System.Object[]] 实例,其中 $null 作为唯一元素).

This behavior of @() is consistent with its purpose (see below): since $null is not an array, @() wraps it in one (resulting in a [System.Object[]] instance with $null as the only element).

在 PetSerAl 的其他示例中,@() 使用 New-Object 创建的数组和集合的行为 - 可能令人惊讶 - 见下文.

In PetSerAl's other examples, @()'s behavior with New-Object-created arrays and collections - may be surprising - see below.

@()目的,array-subexpression operator,松散地说,是为了确保表达式/命令的结果被视为数组,即使它恰好是一个标量(单个对象).:

  • @(...) 收集封闭的命令的输出原样/封闭的表达式的 枚举输出 - always new - [object[]] 数组,即使只有一个单个输出对象.

  • @(...) collects an enclosed command's output as-is / an enclosed expression's enumerated output in an - always new - [object[]] array, even if there's only a single output object.

@(...) 永远需要数组文字(在 v5.1+ 它已被优化) - 使用 数组构造器运算符 本身通常就足够了,例如,'foo', 'bar' 而不是 @('foo', 'bar') - 但它在以下情况下很有用:

@(...) is never needed for array literals (in v5.1+ it is optimized away) - use of ,, the array constructor operator by itself is generally sufficient, e.g., 'foo', 'bar' instead of @('foo', 'bar') - but it is useful in the following cases:

  • 创建一个数组:@())

创建一个单元素数组 - 例如@('foo') - 这比 ,unary 形式更容易阅读,否则需要 - 例如, 'foo'

to create a single-element array - e.g. @('foo') - which is easier to read than the unary form of , that would otherwise be required - e.g. , 'foo'

为了语法方便:将概念上的数组字面量扩展到多行,而不必使用 , 来分隔元素,也不必包含命令在 (...) 中;例如:

for syntactic convenience: to spread what is conceptually an array literal across multiple lines without having to use , to separate the elements and without having to enclose commands in (...); e.g.:

@(
  'one'
  Write-Output two
)

陷阱:

  • @() 不是数组构造函数,而是一个担保人":因此,@(@(1,2))不会创建嵌套数组:

  • @() is not an array constructor, but a "guarantor": therefore, @(@(1,2)) does not create a nested array:

  • @(@(1, 2)) 实际上与 @(1, 2) 相同(并且只是 1, 2).事实上,每个额外的@() 都是一个昂贵的空操作,因为它只是创建了前一个输出的数组的副本.
  • 使用的一元形式,数组构造运算符,构造嵌套数组:
    , (1, 2)
  • @(@(1, 2)) is effectively the same as @(1, 2) (and just 1, 2). In fact, each additional @() is an expensive no-op, because it simply creates a copy of the array output by the previous one.
  • Use the unary form of , the array constructor operator, to construct nested arrays:
    , (1, 2)

$null@() 认为是一个单个对象,因此产生一个包含元素的单元素数组$null:

$null is considered a single object by @(), and therefore results in a single-element array with element $null:

  • @($null).Count1

命令调用输出单个数组作为一个整体结果是一个嵌套数组:

Command calls that output a single array as a whole result in a nested array:

  • @(Write-Output -NoEnumerate 1, 2).Count1

表达式中,将任何类型的集合包装在@()枚举并且总是在一个(新的)[object[]] 数组中收集元素:

In an expression, wrapping a collection of any type in @() enumerates it and invariably collects the elements in a (new) [object[]] array:

  • @([System.Collections.ArrayList] (1, 2)).GetType().Name 返回 'Object[]'

如果需要,请继续阅读以获取更多详细信息.

Read on for more detailed information, if needed.

详情:

@() 行为如下: 感谢 PetSerAl 的广泛帮助.

  • PSv5.1+(Windows PowerShell 5.1 和 PowerShell [Core] 6+)中,使用一个直接构造数组的表达式 使用 数组构造函数运算符优化@():

  • In PSv5.1+ (Windows PowerShell 5.1 and PowerShell [Core] 6+), using an expression that directly constructs an array using ,, the array constructor operator, optimizes @() away:

  • 例如,@(1, 2)1, 2@(, 1) 相同和 , 1 一样.

  • E.g., @(1, 2) is the same as just 1, 2, and @(, 1) is the same as just , 1.

在使用 just , 构造的数组的情况下 - 产生一个 System.Object[] 数组 - 这种优化很有帮助,因为它节省了首先展开该数组然后重新打包它的不必要步骤(见下文).
据推测,这种优化是由于使用 @( ..., ..., ...) 来构造数组的普遍且以前效率低下的做法,源于错误的信念 @() 用于构造数组.

In the case of an array constructed with just , - which yields a System.Object[] array - this optimization is helpful, because it saves the unnecessary step of first unrolling that array and then repackaging it (see below).
Presumably, this optimization was prompted by the widespread and previously inefficient practice of using @( ..., ..., ...) to construct arrays, stemming from the mistaken belief that @() is needed to construct an array.

但是,在Windows PowerShell v5.1 中,优化也意外在构造数组时应用使用 cast特定类型,例如 [int[]](该行为已在 PowerShell [Core] 6+ 和更旧的 Windows PowerShell 版本受到影响);例如,
@([int[]] (1, 2)).GetType().Name 产生 Int32[].这是@()返回其他而不是System.Object[]的唯一情况,并假设它总是可能导致意外错误和副作用;例如:
@([int[]] (1, 2))[-1] = 'foo' 中断.
$a = [int[]] (1, 2);$b = @([int[]] $a) 意外地没有创建 new 数组 - 请参阅 这个 GitHub 问题.

However, in Windows PowerShell v5.1 only, the optimization is unexpectedly also applied when constructing an array with a specific type using a cast, such as [int[]] (the behavior has been corrected in PowerShell [Core] 6+ and older Windows PowerShell versions are not affected); e.g.,
@([int[]] (1, 2)).GetType().Name yields Int32[]. This is the only situation in which @() returns something other than System.Object[], and assuming that it always does can lead to unexpected errors and side effects; e.g.:
@([int[]] (1, 2))[-1] = 'foo' breaks.
$a = [int[]] (1, 2); $b = @([int[]] $a) unexpectedly doesn't create a new array - see this GitHub issue.

否则:如果 @(...) 中的(第一个)语句是一个 表达式,恰好是一个collection,该集合是枚举命令的(通常是一对一的流)输出按原样收集;在任何一种情况下,对象的结果计数决定了行为:

Otherwise: If the (first) statement inside @(...) is an expression that happens to be a collection, that collection is enumerated; a command's (typically one-by-one streaming) output is collected as-is; in either case the resulting count of objects determines the behavior:

  • 如果结果是一个单个项目/包含没有个项目,则结果将包装在单个-[System.Object[]].

  • If the result is a single item / contains no items, the result is wrapped in a single-element / empty array of type [System.Object[]].

  • 例如,@('foo').GetType().Name 产生 Object[]@('foo').Count 产生 1(不过,如前所述,在 PSv3+ 中,您可以直接使用 'foo'.Count).
    @( & { } ).Count 产生 0(执行一个空的脚本块会输出一个空集合")([System.Management.Automation.Internal.AutomationNull]::Value)

  • E.g., @('foo').GetType().Name yields Object[] and @('foo').Count yields 1 (though, as stated, in PSv3+, you can use 'foo'.Count directly).
    @( & { } ).Count yields 0 (executing an empty script block outputs a "null collection" ([System.Management.Automation.Internal.AutomationNull]::Value)

警告:@() 围绕创建数组的 New-Object 调用/集合输出该数组/collection 包裹在单元素外部数组中.

Caveat: @() around a New-Object call that creates an array / collection outputs that array/collection wrapped in a single-element outer array.

  • @(New-Object System.Collections.ArrayList).Count 产生 1 - 空数组列表包含在单个元素 中System.Object[] 实例.

  • @(New-Object System.Collections.ArrayList).Count yields 1 - the empty array list is wrapped in a single-element System.Object[] instance.

原因是New-Object,由于是一个命令(例如一个cmdlet调用)是不受枚举(解包)影响,导致 @() 只能看到一个 单个 项目(恰好是一个数组/集合),因此它包装在一个单项数组中.

The reason is that New-Object, by virtue of being a command (such as a cmdlet call) is not subject to enumeration (unwrapping), causing @() to see only a single item (which happens to be an array/collection), which it therefore wraps in a single-item array.

令人困惑的是,当您使用表达式来构造数组/集合时,不会发生这种情况,因为表达式的输出是枚举(展开,展开):

What may be confusing is that this does not happen when you use an expression to construct an array / a collection, because the expression's output is enumerated (unwrapped, unrolled):

  • @([system.collections.arraylist]::new()).Count 产生 0;表达式输出一个被枚举的空集合,由于没有枚举,@() 创建一个空的 System.Object[] 数组.
    请注意,在 PSv3+ 中,只需使用一组额外的括号 ((...)) 和 New-Object - 将 New-Object command表达式 - 将产生相同的结果:
    @((New-Object System.Collections.ArrayList)).Count 也产生 0.
  • @([system.collections.arraylist]::new()).Count yields 0; the expression outputs an empty collection that is enumerated, and since there's noting to enumerate, @() creates an empty System.Object[] array.
    Note that, in PSv3+, simply using an extra set of parentheses ((...)) with New-Object - which converts the New-Object command to an expression - would yield the same result:
    @((New-Object System.Collections.ArrayList)).Count yields 0 too.

如果结果包含多个,这些项将作为常规 PowerShell 数组返回([System.Object[]]);例如:

If the result comprises multiple items, these items are returned as a regular PowerShell array ([System.Object[]]); e.g.:

  • 使用命令:
    • $arr = @(Get-ChildItem *.txt)Get-ChildItem 的一对一流输出收集到一个 System.Object[] 数组
    • With a command:
      • $arr = @(Get-ChildItem *.txt) collects the one-by-one streaming output from Get-ChildItem in a System.Object[] array
      • $arr = [int[]] (1, 2);@($arr) 枚举 [int[]] 数组 $arr 然后重新打包元素作为 System.Object[] 数组.

      • $arr = [int[]] (1, 2); @($arr) enumerates [int[]] array $arr and then repackages the elements as a System.Object[] array.

      注意这个过程的低效类型保真度的潜在损失:原始数组被枚举并收集在一个em>new 数组,总是System.Object[] 类型;有效的替代方法是转换为[array](也适用于命令):[数组] $result = ...

      Note the inefficiency and potential loss of type fidelity of this process: the original array is enumerated and collected in a new array that is always of type System.Object[]; the efficient alternative is to cast to [array] (which also works with commands): [array] $result = ...

      这篇关于没有 '.Count' 属性的对象 - 使用 @()(数组子表达式运算符)与 [Array] 转换的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆