Pau*_*l π 4 .net powershell scalar null
考虑以下示例:
\n$nullforeach ($n in $null) {'This is a $null test'}\n(no output)\n\n$null | ForEach-Object {'This is a $null test'}\nThis is a $null test\n\n$null -in $null\nTrue\n\n$null -contains $null\nTrue\nRun Code Online (Sandbox Code Playgroud)\n\xc2\xa0\n
[int]1foreach ($n in [int]1) {'Test'}\nTest\n\n[int]1 | ForEach-Object {'Test'}\nTest\n\n[int]1 -in [int]1\nTrue\n\n[int]1 -contains [int]1\nTrue\nRun Code Online (Sandbox Code Playgroud)\n
\n由于 $null 是一个不包含任何内容的标量值,因此在第二个 $null 示例中,$null 将沿着管道发送一个“nothing”的单个实例,这解释了输出。
$null为什么即使$null -in $null返回也不能作为集合进行迭代True作为集合进行迭代?
tl;dr
The asymmetry you've observed is definitely unfortunate - but is unlikely to get resolved, so as not to break backward compatibility (see the bottom section for a potential solution).
Both
<commandOrExpression> | ... (pipeline) and
foreach ($var in <commandOrExpression>) { ... }
are enumeration contexts:
If <commandOrExpression> evaluates to an object that is enumerable,[1] it is automatically enumerated and the enumerated elements are processed one by one.
If it doesn't, the result is processed as itself; in a manner of speaking: it is treated like a single-element enumerable.
$null, which corresponds to null in C#, is a special something that happens to represent a "scalar (single) nothing".
This contrasts with the enumerable null - technically the [System.Management.Automation.Internal.AutomationNull]::Value singleton, aka "Automation Null" - which is what you get when a command produces no output.
$null), and enumerating me produces no elements".As an aside: as of PowerShell 7.4.x it is still nontrivial to determine if a given stored value is an actual $null or an enumerable null (a distinction that situationally does matter):
# Only $true if $someValue contains the enumerable null
$null -eq $someValue -and @($someValue).Count -eq 0
Run Code Online (Sandbox Code Playgroud)
The following future enhancement has been green-lit, but has not yet been implemented - see GitHub issue #13465:
# !! Future enhancement - doesn't work as of v7.4.x
$someValue -is [System.Management.Automation.AutomationNull]
Run Code Online (Sandbox Code Playgroud)
Therefore, both the foreach loop and the pipeline:
fortunately do not perform enumeration if the enumerable null is provided as input, given that its very purpose is to signal that there is nothing to enumerate.
# Capture the output of a command that produces NO output.
$noOutput = Get-Item *NoSuchFile*
# No output, because the loop body is not executed.
foreach ($obj in $noOutput) { 'This does not print.' }
# Ditto.
$noOutput | ForEach-Object { 'This does not print' }
Run Code Online (Sandbox Code Playgroud)
should process $null as itself, and therefore result in a single processing iteration with $null as the (non-)object to process:
Fortunately, this is how it works in the pipeline, as evidenced by your sample call to ForEach-Object cmdlet in your question.
Unfortunately, this is not how the foreach language statement behaves, which unexpectedly does not process the $null, as you've also observed.
As for the reason for this asymmetry:
Up to version 2 of Windows PowerShell, assigning the output from a command that produced no output to a variable implicitly - and unexpectedly - converted the enumerable null (representing the lack of output) to $null, which caused the following statements not to be equivalent:
# DIRECT processing of command output - the loop body is NOT entered.
foreach ($obj in Get-Item *NoSuchFile*) { 'This does not print' }
# INDIRECT processing of command output, via an *intermediate variable*.
$output = Get-Item *NoSuchFile*
# In v2, $output was $null, not the enumerable null,
# causing the loop body to execute (once, with $obj containing $null)
# In v3+, this is now - fortunately - equivalent to the above.
foreach ($obj in $output) { 'This does not print' }
Run Code Online (Sandbox Code Playgroud)
In v3, two fundamental changes were made:
$null.$null as foreach input:[2]Given the former v3 change, implementing the latter was not strictly necessary, but presumably:
The latter change was motivated by also making non-existent variables result in no processing.
The assumption was made that if a pipeline is used, intermediate and therefore potentially non-existent variables aren't needed, in which case the problem doesn't arise.
However, the problem (still) does arise in the pipeline, namely if non-existent variables or non-existent properties of existing objects are used as input, because they evaluate to $null and therefore (justifiably) cause $null to be sent through the pipeline.
Also, passing the enumerable null as an argument to a function or script's untyped parameter causes quiet conversion to $null.
It follows from the above that a potential solution that should only minimally impact backward compatibility, if at all, is the following:
Make references to non-existent variables or properties default to the enumerable null rather than $null, which would implicitly prevent enumeration in both the pipeline and in foreach statements.
Additionally, preserve the enumerable null as function / script arguments when binding to untyped parameters.
$null, so something like $null -eq $noSuchVariable would continue to work.Make foreach too process actual $null values as such, as the pipeline already does, which would eliminate the asymmetry.
[1] Specifically, types that implement the IEnumerable interface or its generic counterpart are automatically enumerated, but there are a few exceptions: strings, dictionaries (types implementing IDictionary or its generic counterpart), and System.Xml.XmlNode instances. Additionally, System.Data.DataTable is enumerated too, despite not implementing IEnumerable itself, via its .Rows property.
See this answer for details.
[2] v3 中的这一行为变化于 2012 年在 PowerShell 团队博客上宣布:新的 V3 语言功能,部分,部分“ForEach 语句不会迭代 $null”,
| 归档时间: |
|
| 查看次数: |
109 次 |
| 最近记录: |