JohnLBevan
JohnLBevan

Reputation: 24430

Behaviour of PowerShell when Combining HashTables

Question

Why is $null + @{} valid, but @{} + $null not; even where null is cast to a hashtable (@{} + ([hashtable]$null)).

Example Code

[hashtable]$a = @{demo1=1;demo2='two'} 
[hashtable]$b = @{demo3=3;demo4='Ivy'} 
[hashtable]$c = $null

#combining 2 hashtables creates 1 with both hashes properties (would error if any properties were common to both)
write-verbose 'a + b' -Verbose
($a + $b)

#combining a null hashtable with a non-null hashtable works
write-verbose 'c + a' -Verbose
($c + $a)

#combing 2 null hashtables is fine; even if we've not explicitly cast null as a hashtable
write-verbose 'c + null' -Verbose
($c + $null)

#however, combinging a hashtable with null (i.e. same as second test, only putting null as the right argument instead of the left, produces an error
write-verbose 'a + c' -Verbose
($a + $c)

Output

Name                           Value                                                                                                                                                                                                                 
----                           -----                                                                                                                                                                                                                 
demo3                          3                                                                                                                                                                                                                     
demo4                          Ivy                                                                                                                                                                                                                   
demo1                          1                                                                                                                                                                                                                     
demo2                          two                                                                                                                                                                                                                   
VERBOSE: c + a
demo1                          1                                                                                                                                                                                                                     
demo2                          two                                                                                                                                                                                                                   
VERBOSE: c + d
VERBOSE: a + c
A hash table can only be added to another hash table.
At line:19 char:1
+ ($a + $c)
+ ~~~~~~~~~
    + CategoryInfo          : InvalidOperation: (:) [], RuntimeException
    + FullyQualifiedErrorId : AddHashTableToNonHashTable

Side Note

Incidentally, this led me to discover this useful trick for a null-coalescing operation for hashtables: ($c + @{}). e.g. ($a + ($c + @{})) avoids the error produced above / (($a + @{}) + ($c + @{})) gives us a completely safe way to add hashtables where either value may be null.

Upvotes: 2

Views: 250

Answers (1)

TessellatingHeckler
TessellatingHeckler

Reputation: 29013

I tried to find out exactly why, but I can't for sure.

My original beliefs were:

  • There may be some PowerShell implicit type coercion happening, where the thing on the right is cast to match the type on the left. e.g. with "1"+1 the 1 on the right becomes a string and the output is "11", but 1+"1" the right becomes a number and the output is 2.
    • This definitely is not happening, or $null + @{} would either throw a cast error, or cast and do null + null = null, not output an empty hashtable as it does.
  • Addition of A + B for things other than numbers has some basic rules for things like array concatenation, but beyond that, it will fall down to the .Net Framework underneath and will try to do a method call like A.op_Addition(B) and it will be up to the thing on the left to say what happens when you try to add the thing on the right to it.
    • This does happen, but is not happening here. Consider @{} + 0 tells you that a hashtable can only be added to a hashtable. (Lies, you can add it to null). And 0 + @{} tells you that hashtables do not contain an op_Addition method. So the addition with null seems like it should give that error, but doesn't, because it's not falling back to this mechanism.

The PSv3 language spec (which I think is the latest version available), download here: https://www.microsoft.com/en-us/download/details.aspx?id=36389, mentions addition:

7.7.1 Addition Description: The result of the addition operator + is the sum of the values designated by the two operands after the usual arithmetic conversions (§6.15) have been applied.

The usual arithmetic conversions say:

If neither operand designates a value having numeric type, then [..] all operands designating the value $null are converted to zero of type int and the process continues with the numeric conversions listed below.

That implies $null gets converted to 0 in both your examples. Although that cannot be happening because 0 + @{} and @{} + 0 are both errors.

There is an explicit mention of adding of two hashtables in section 7.7:

7.7.4 Hashtable concatenation Description: When both operands designate Hashtables the binary + operator creates a new Hashtable that contains the elements designated by the left operand followed immediately by the elements designated by the right operand.

OK, hashtable addition is handled by the PowerShell engine, but only adding two hashtables.

Section 7. introduction says:

Windows PowerShell: If an operation is not defined by PowerShell, the type of the value designated by the left operand is inspected to see if it has a corresponding op_ method.

And the operation $null + Hashtable doesn't seem to be defined by PowerShell from what I can see in the spec, and the corresponding method for + is op_Addition - a method which hashtables do not have, see error code earlier - and this is not throwing that error, and not only that but in the case of adding to 0 the error comes from the Right operand not the left one.

And the other interesting bit in the spec is:

4.1.2 The characteristics of the null type are unspecified.


So the summary appears to be:

  • @{} + $null - is triggering the PowerShell handling of adding two hashtables, even though the spec doesn't say that it will.
  • $null + @{} - it looks like there's an implicit $null + x = x rule, although the spec doesn't seem to mention it, and it might be implementation dependent.
  • [<type>]$null - casting $null to a numeric type results in 0 (6.15 arithmetic conversions), but casting it to anything else(?) appears to result in $null (not in the spec).
  • The comment and linked chain which says $null has no type are against the PowerShell spec 4.1.2 "the null type" which says "The null type has one instance, the automatic variable $null (§2.3.2.2), also known as the null value. The characteristics of this type are unspecified." so at least in terminology, it's described as a type in PowerShell even if you can't GetType() on it ..

Upvotes: 2

Related Questions