Foreach should not execute the loop body for a scalar value of $null - by Keith Hill MVP

Status : 

  Fixed<br /><br />
		This item has been fixed in the current or upcoming version of this product.<br /><br />
		A more detailed explanation for the resolution of this particular item may have been provided in the comments section.

Sign in
to vote
ID 281908 Comments
Status Closed Workarounds
Type Bug Repros 11
Opened 6/7/2007 12:50:29 PM
Access Restriction Public


I like that foreach iterates over a scalar value in addition to collections.  However I see no scenario where executing the loop body when the scalar value is $null is actually useful!
Sign in to post a comment.
Posted by Keith Hill MVP on 1/26/2011 at 8:25 AM
"Closed as Fixed" - Woohoo! Snoopy dance!
Posted by johndog on 5/14/2010 at 5:35 PM
Generally, a null pointer needs to be interpreted in terms of the type of object it might have been referring to. In the context of foreach, psh *should* be assuming a typeless $null is actually referring to a non-existant collection, not assuming it is a scalar.
Posted by cholland on 1/7/2010 at 5:39 AM
I have to agree with r_keith_hill's last point which I think sums it up very well - the lack of equivalence between A and B is really counter intuitive. Even when you know about it, you'll still get caught with this one. It feels like I need to establish the a standard pattern of "if($array -ne $null) { foreach(...) { ...} }" to protect every foreach statement.
Posted by 6Doughnuts on 12/1/2009 at 2:23 AM
The example given about an array of nulls makes a lot of sense in terms of how foreach works. I'd say then that the problem lies with Get-ChildItem and the fact that it returns inconsistently. As most usage scenarios of this cmdlet would have it returning a collection, shouldn't it always return collections? Wouldn't it make sense to return an empty collection when there are no items?
Posted by Henry Gabryjelski - MSFT on 10/6/2009 at 2:33 PM
It doesn't matter if this is by design. This violates a basic axiom of not surprising the writer in edge cases. Please re-consider and fix.
Posted by Microsoft on 1/12/2009 at 7:18 PM
The current behaviour is by design but requires an explanation. In PowerShell, the value null is a scalar value. A collection can contain multiple nulls in which case a foreach runs multiple times:

    foreach ($i in $null,$null,$null) { } # runs three times
    foreach ($i in $null, $null) {} # runs twice
    foreach ($i in $null) {} # runs *once* as one would expect from this sequence.

The example in the repro is an instance where this behaviour, while consistent and reasoanble (i.e. one can infer the behaviour by a set of rules) it is inconvenient. For this reason, PowerShell has special syntax to handle address this scenario. If the pipeline in question is always required to return an enumerable collection, then it can be run inside of @( ... ) as in

    PS (MTA-V1) (39) > $files = @( gci *.snoopydance )
    PS (MTA-V1) (40) > foreach ($file in $files) { [datetime]::SpecifyKind($file.LastWriteTime, 'Utc') }
    PS (MTA-V1) (41) >

Now there is no issue - the body of the foreach command is never run because $files contains an empty collection instead of null.
Posted by Keith Hill MVP on 9/1/2007 at 5:49 PM
I think that the "evil" in PowerShell lies in the fact that there isn't equivalence between A and B:

function Empty {}

A: $tmp = Empty
$result = @($tmp) # result will be a 1 element array ($null)

B: $result = @(Empty) # result will be an empty array

Surely you can see the problem with this, right? Moving thru an intermediate variable like $tmp shouldn't change the final outcome IMO.
Posted by Keith Hill MVP on 6/12/2007 at 9:19 AM
One other thought here, this is the kind of issue that gets me in trouble with using PowerShell on my team. Other team members (having used Korn shell for years) like the compile time type safety of creating tools in C#. I try to point out the productivity gains of using PoSh for simple little tools but when I waste time fixing niggly little bugs caused by the issue it *really* waters down my productivity argument.
Posted by Keith Hill MVP on 6/12/2007 at 9:10 AM
BTW it is also a language oddity that this doesn't work:

$resolvedSrc = resolve-path $src
foreach ($file in @($resolvedSrc)) { ... }

Now I understand that $null is a scalar value and we've just told PoSh to make an array out of a single scalar value of $null. But it is going to trip people up IMO. Perhaps the language needs the notion of no value or empty value e.g. $nil?
Posted by Keith Hill MVP on 6/12/2007 at 9:03 AM
I realize the $null is a scalar value but the whole point of executing a loop over a set of data (or even a scalar data value) is to execute some code on each data value in the set. When you execute code on the data value $null, usually bad things happen like if you pass a null to a .NET method you get an ArgumentNullException. Just today, I got bit by this "bug" AGAIN. Here's the code that got me:

    $resolvedSrc = resolve-path $src
    foreach ($file in $resolvedSrc) {
        if (!(test-path $file -pathtype leaf)) {
            $OFS = ", "
            throw "CopyFile - `$src must contain only files: $resolvedSrc"

If no files exist at $src then the foreach iterates with $file equal to $null and test-path blows chunks on this. IMO this is much too easy to shoot yourself in the foot. Go ahead and treat $null as a scalar value but I don't see why the implementation of foreach can't be special cased to not execute the loop body on a scalar value of $null?
Posted by Microsoft on 6/8/2007 at 8:26 AM
This is by design. $null is treated as a scalar value.
You can use array subexpression to force $files to be an array.
$files = @(gci *.snoopydance)