Search code examples
powershelldebuggingbreakpointsnull-string

Why does [NullString]::Value evaluate differently with a breakpoint?


I've tried this code in PowerShell ISE and VS Code with the same strange result. Without a breakpoint, the output is EMPTY, but with a breakpoint in the line with "NULL", the output is NULL (as expected). Why?

function demo {
    param(
        [string] $value = [NullString]::Value
    )

    if ($null -eq $value) {
        "NULL"
    } elseif ($value -eq '') {
        "EMPTY"
    } else {
        "$value"
    }
}

demo

I know now, that PowerShell will always convert a non-string value (e.g. $null or [NullString]::Value) to an (empty) string, when you use the type modifier [string] for a parameter. Fine, I can live with that, but it's hard to figure that out by yourself, if debugging is so weird in this case.


Solution

  • PetSerAl, as many times before, has provided the crucial pointer in a comment on the question:

    A known optimization bug is the most likely cause (as of Windows PowerShell v5.1 / PowerShell Core v6.1.0), and that bug just happens to be masked when the code is run in the Visual Studio Code or ISE debugger.

    You can therefore use the same workaround mentioned in the linked bug report: place a call to Remove-Variable anywhere in the function body (its presence is enough - the call needn't actually be made at runtime):

    function demo {
        param(
            [string] $value = [NullString]::Value
        )
    
        # Workaround for optimization bug
        if ($False) { Remove-Variable }
    
        if ($null -eq $value) {
            "NULL"
        } elseif ($value -eq '') {
            "EMPTY"
        } else {
            "$value"
        }
    }
    
    demo
    

    Now you consistently get "NULL" as the output, whether debugging or not.

    However, it's best to restrict use of [NullString]::Value to what it was designed for: passing null to string-typed parameters of .NET methods - see below.


    As for why use of [NullString]::Value is needed at all in order to pass $null to a string parameter / store $null in a [string] variable, given that .NET strings can normally store null ($null) directly:

    By (historical) design, PowerShell converts $null to '' (the empty string) when you assign it to a [string] variable; here's the rationale:

    From https://github.com/PowerShell/PowerShell/issues/4616#issuecomment-323530442:

    The thinking behind the design was that in most ways, $null and the empty string both represent the same error condition and that in the rare case where a distinction was important, $PSBoundParameters would be sufficient to distinguish between knowing a value was provided or not.

    Given that even passing $null directly performs conversion to '' when passing arguments to string-typed .NET methods, you couldn't pass null to such methods up to v2.
    To remedy that, version 3 introduced [NullString]::Value, which explicitly signals the desire to pass $null in a string context.
    (The alternative - making PowerShell strings default to $null and allowing direct assignment of $null - was considered a change that would break too many existing scripts.)

    Using [NullString]::Value beyond its intended purpose - i.e., for passing null to string parameters in .NET methods - is problematic, given that PowerShell doesn't expect [string] variables to contain $null in other contexts.

    Fixing the above-mentioned optimization bug would help in the scenario in the question, but there may be other pitfalls.