.netpowershellnullscalar

Why does $null behave differently in ForEach-Object {} vs foreach()?


Considering the following examples:

$null

foreach ($n in $null) {'This is a $null test'}
(no output)

$null | ForEach-Object {'This is a $null test'}
This is a $null test

$null -in $null
True

$null -contains $null
True

 

[int]1

foreach ($n in [int]1) {'Test'}
Test

[int]1 | ForEach-Object {'Test'}
Test

[int]1 -in [int]1
True

[int]1 -contains [int]1
True


Since $null is a scalar value that contains nothing, in the second $null example, $null will send a single instance of 'nothing' down the pipeline, which explains the output.

Why can't $null be iterated over as a collection even though $null -in $null returns True?


Solution

  • 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:



    Therefore, both the foreach loop and the pipeline:


    As for the reason for this asymmetry:


    It follows from the above that a potential solution that should only minimally impact backward compatibility, if at all, is the following:


    [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] This change in behavior in v3 was announced on the PowerShell team blog in 2012: New V3 Language Features, section, section "ForEach statement does not iterate over $null",