¿Por qué los operadores de comparación de PowerShell no enumeran colecciones de tamaño 1?
Al verificar la nulidad de variables y colecciones de variables, los operadores de comparación parecen enumerar colecciones de tamaño 2 o más:
> if ( @( $null, $null ) -eq $null ) { $True } else { $False }
True
Pero no lo hacen para colecciones de tamaño 1:
> if ( @( $null ) -eq $null ) { $True } else { $False }
False
Soy consciente de que es una buena práctica realizar una comparación nula utilizando el lado izquierdo ( $null -eq @( $null )
), pero ¿alguien puede explicar qué está sucediendo aquí? Sospecho que está sucediendo algo más sutil que afecta el resto del código que escribo.
¿Por qué estos dos resultados son diferentes?
tl; dr
[bool]
En contextos y conversiones booleanos condicionales/implícitos de PowerShell :
Las matrices de un solo elemento se tratan como escalares : es decir, su único elemento se interpreta como booleano. [1]
Las matrices de más de 2 elementos siempre son
$true
, independientemente de su contenido.
Con una matriz como LHS , los operadores que reconocen la matriz, como -eq
invariablemente, también generan una matriz .
Dado que los elementos de su matriz son todos $null
y se compara con $null
, su comparación es una operación no operativa efectiva (por ejemplo, @( $null ) -eq $null
da como resultado @( $null )
) y sus condicionales son equivalentes a:
[bool] @( $null, $null ) # -> $true - array with 2+ elements is always $True
[bool] @( $null ) # -> $false(!) - treated like: [bool] $null
Quizás sea sorprendente que la lógica booleana implícita aplique la lógica de canalización a una matriz :
Es decir, una matriz de un solo elemento se desenvuelve (conceptualmente) y su elemento se interpreta como booleano.
Por lo tanto, [bool] @( $null )
se trata igual que [bool] $null
, que es $false
.
Generalmente, @( <one-and-only-element> )
(o , <one-and-only-element>
) se trata igual que <one-and-only-element>
en un contexto booleano.
Por el contrario, si una matriz tiene 2 o más elementos , siempre está $true
en un contexto booleano , incluso si se consideraran todos sus elementos individualmente $false
.
Solución alternativa para probar si una matriz arbitraria está vacía :
Base su condicional en la .Count
propiedad:
if ( (<array>).Count ) { $true } else { $false }
Podrías agregar -gt 0
, pero eso no es estrictamente necesario, porque cualquier valor distinto de cero es implícitamente $true
.
Aplicado a su ejemplo:
PS> if ( ( @($null) -eq $null ).Count ) { $true } else { $false }
True
Probando que un valor arbitrario sea un (escalar) $null
:
if ($null -eq <value>) { $true } else { $false }
Tenga en cuenta que $null
debe usarse como LHS para evitar que la lógica de filtrado de matriz tenga efecto; debe <value>
ser una matriz.
Esa es también la razón por la cual Visual Studio Code con la extensión PowerShell recomienda "$null debe estar en el lado izquierdo de las comparaciones" si escribe algo como $var -eq $null
.
[1] Resumen de conversión a booleana :
Entre escalares (no colecciones) :
Lo siguiente está implícito
$false
:''
/""
(cuerda vacía)0
(de cualquier tipo numérico).$null
, incluido el valor "nulo enumerable" que indica la ausencia de salida de un comando (consulte esta respuesta para obtener información general); por ejemplo, ambos
[bool] $null
y[bool] (Get-ChildItem NoSuchFiles*)
rendimiento$false
.Error : comparar
$null
el "nulo enumerable" con un booleano explícitamente es-eq
siempre ,$false
incluso con$null
el RHS (a pesar de que el RHS normalmente se ve obligado al tipo de LHS):# !! -> $false # By contrast, `$false -eq [bool] $null` is $true $false -eq $null # Ditto for the "enumerable null" # !! -> $false $false -eq (Get-ChildItem NoSuchFiles*)
Error : cualquier cadena que no esté vacía es siempre
$true
:por ejemplo,
[bool] 'False'
es$true
Tenga en cuenta que esto difiere del análisis explícito de cadenas :
[bool]::Parse('false')
devuelve$false
(y$true
para'true'
, pero no reconoce nada más).
Las instancias de cualquier otro tipo que no sea de colección son siempre
$true
implícitamente , incluidas las de tipo[pscustomobject]
y[hashtable]
(que PowerShell trata como un objeto único, no como una colección de entradas).- Desafortunadamente, esto incluye tipos que definen
[bool]
operadores de conversión .NET explícitos, lo que significa que estos operadores, en su mayoría, no se respetan; ver esta respuesta .
- Desafortunadamente, esto incluye tipos que definen
- Entre colecciones como matrices (más exactamente, tipos similares a colecciones que implementan la
IList
interfaz; consulte el código fuente ):Las colecciones vacías siempre
$false
son.Error : las colecciones de un solo elemento se evalúan como ese elemento :
Si el único elemento es un escalar (no colección): su valor booleano.
- Por ejemplo,
[bool] @(0)
es$false
, mientras que[bool] @(42)
es$true
- Por ejemplo,
Si ese elemento es en sí mismo una colección :
$true
si tiene al menos 1 elemento, independientemente de cuál sea ese elemento .- Por ejemplo,
[bool] (,, $false)
es$true
(una matriz irregular: una matriz de un solo elemento cuyo único elemento es en sí mismo una matriz (de un solo elemento))
- Por ejemplo,
Las colecciones de más de 2 elementos siempre
$true
son , independientemente de los valores de sus elementos .- Por ejemplo,
[bool] @($null, $false)
es$true
.
- Por ejemplo,
Los siguientes elementos se evalúan como $false
:
@()
0
$null
$false
''
En su primer ejemplo:
@($null, $null) -eq $null
Esto evalúa $null, $null
cuál es una colección distinta de cero, por lo que lo es $true
. Puedes observar esto con lo siguiente:
[bool]($null, $null)
En su segundo ejemplo, lo que está observando es el filtrado de una matriz como en el primer caso, pero devolviendo un escalar (en lugar de una matriz) ya que solo un elemento de la matriz coincidió con el filtro:
@($null) -eq $null
Esto se evalúa como, @($null)
pero PowerShell lo evalúa como un escalar en un contexto booleano, por lo que devuelve $false
, observado por:
[bool]@($null)
Nota al pie: en powershell v2 , hubo un error con el filtrado que generó la comparación $null
de la izquierda . $null
Este error provocó que if/else
los bloques se omitieran por completo.