当绑定到参数时,如何防止字符串参数从null更改为空?

alx*_*x9r 6 string parameters powershell null

请考虑以下代码:

function f {
    param (
        [AllowNull()]
        [string]
        $x
    )
    return $x
}

$r = f -x $null
Run Code Online (Sandbox Code Playgroud)

$null到达[string]::Empty时转换为return. $null是不同的[string]::Empty,我想保留这种区别.我也更喜欢保持$x类型,[string]因为$x只有字符串的含义,接口在别处使用.

  1. 我怎样才能让$x出来作为$null当它传递$null
  2. 还有其他方法我可以告诉它不是从里面$x传递的吗?$null [string]::Emptyf

更新1

我想要做的是为其他类型.以下是相同的概念[int]:

function f { 
    param( 
        [System.Nullable[int]]$x 
    )
    return $x 
}

$r = f -x $null
Run Code Online (Sandbox Code Playgroud)

在那种情况下$r确实如此$null. $x可以是$null[int],但没有别的.对我来说似乎很奇怪,我必须允许任何物体,所以我可以通过一个$null或多个[int].

[System.Nullable[string]]产生一个错误,归结为[System.Nullable[T]]要求[T]是一个值类型. [string]是一种引用类型,因此不起作用.


更新2

似乎可以通过$null而不会导致转换为任何类型的参数除外 [string].我测试了以下内容:

function f { param([System.Nullable[int]]$x) $x }
function f { param([System.Nullable[System.DayOfWeek]]$x) $x }
function f { param([hashtable]$x) $x }
function f { param([array]$x) $x }
function f { param([System.Collections.Generic.Dictionary[string,int]]$x) $x }
function f { param([System.Collections.ArrayList]$x) $x }
function f { param([System.Collections.BitArray]$x) $x }
function f { param([System.Collections.SortedList]$x) $x }
function f { param([System.Collections.Queue]$x) $x }
function f { param([System.Collections.Stack]$x) $x }
Run Code Online (Sandbox Code Playgroud)

传递$null给任何这些函数输出$ null.的唯一参数类型我还没有找到一种方法,其通过$null无需转换[string].


更新3

PowerShell在这方面的行为也与C#不一致.C#中的相应功能如下:

public string f(string x)
{
    return x;
}
Run Code Online (Sandbox Code Playgroud)

致电f(null)回报null.


更新4

显然[NullString]::Value是为了解决这个问题. 我似乎努力传递nullstringC#API中的参数.但是,在PowerShell中[NullString]::Value转换为[string]::empty相同的$null.请考虑以下代码:

function f {
    param (
        [AllowNull()]
        [string]
        $x
    )
    return $x
}

$r = f -x ([NullString]::Value)
$r.GetType().Name
Run Code Online (Sandbox Code Playgroud)

执行该代码输出String.$r[string]::Empty尽管[NullString]::Value传递给$x.


更新5

PowerShell团队表示这是设计的:

这是设计和......改变行为将是一个巨大的突破性变化.

该主题涉及有关其背后推理的有趣讨论.我怀疑这个行为的某些后果在做出决定时没有被理解,因为行为直接违反了PowerShell cmdlet"强烈鼓励设计指南"SD03,其部分内容如下:

如果您的参数需要区分3个值:$ true,$ false和"unspecified",则定义Nullable类型的参数.当cmdlet可以修改对象的布尔属性时,通常会出现对第三个"未指定"值的需求.在这种情况下,"未指定"表示不更改属性的当前值.

mkl*_*nt0 5

总结和补充问题、答案和评论中的信息:

长话短说:博士

最好不要反对 PowerShell 不允许[string]变量的设计$null,并限制使用[NullString]::Value来调用 .NET 方法。


  • 当PowerShell分配给类型的 [parameter] 变量时,它会转换$null为(空字符串),并且参数变量也默认为 .''[string]''

    • 唯一的例外是在 PSv5+ 自定义类中使用未初始化的 [string] 属性,正如 alxr9(OP)指出的那样:class c { [string] $x }; $null -eq ([c]::new()).x确实会产生$True暗示该属性.x包含$null. 但是,此异常可能是意外的,也可能是一个 bug,因为当您使用该属性初始化该属性$null$null稍后对其进行赋值时,会''再次开始转换;类似地,使用return $nullfrom a[string]类型的方法输出''

    • 除了例外之外,PowerShell 的行为与C#字符串变量/参数不同,您可以直接分配/传递给它们null,并且在某些上下文中默认为这些变量/参数nullstring是 .NET引用类型,并且此行为适用于所有引用类型。
      (由于引用类型实例本质上可以包含null,因此不需要通过 单独的可空包装器System.Nullable`1,这确实不受支持(它仅适用于值类型)。)

  • 正如问题(更新 5)中所指出的,PowerShell 与 C# 行为的背离是设计造成的,并且它的改变并不是仅仅出于向后兼容性的原因的选择。

  • [NullString]::Value在 v3 中专门引入,以允许传递null.NET 方法string的参数- 虽然没有明确阻止或阻止在纯 PowerShell 代码中使用,但更新 4 中的意外行为以及核心 PowerShell 团队成员的评论(见下文)表明这种用途是没有预料到的。

    • 警告:虽然可以[NullString]::Value在纯PowerShell 代码中使用,但可能存在超出下面讨论的陷阱,因为从未打算在调用 .NET 方法的上下文之外使用[NullString]::Value;引用PowerShell团队核心成员的话

C# 方法的参数是 的目标场景[NullString]::Value,我想说这可能是唯一合理的场景。

  • 解决方法是将您的(参数)变量键入为[object]或根本不对其进行类型约束,这相当于相同的。此类变量很乐意接受$null,但请注意,您可能必须自己对[string]非值进行字符串化(转换为 )$null(尽管 PowerShell 在显式或隐式字符串上下文中自动为您执行此操作) - 请参阅下面的倒数第二个代码示例。

尽管有上面的建议,如果您确实需要一个[string]可以传递$null给 via[NullString]::Value的参数变量,如您问题中的更新 4 所示,则有一个 - 晦涩 -解决方法来解决阻止您的代码工作的优化错误,这要归功于以下的侦查宠物服务器

function f {
    param (
        [string] $x
    )
    # Workaround; without this, even passing [NullString]::Value 
    # returns '' rather than $null            
    if ($False) { Remove-Variable } 
    return $x
}

$r = f -x ([NullString]::Value)
$r.GetType().Name  # now fails, because $r is $null
Run Code Online (Sandbox Code Playgroud)

请注意,当分配/传递[NullString]::Value[string]-typed [parameter] 变量时,它会立即转换为(在参数$null变量的情况下,仅当错误得到修复或解决方法到位时)。然而,一旦以这种方式成功存储在变量中,它显然可以这样传递(同样,只有在错误得到修复或解决方法到位的情况下)。$null


如果您不想依赖解决方法/等待修复和/或不想给调用者带来必须通过[NullString]::Value而不是 的负担,您可以以CuriosJason Schnell$null的答案为基础,这些答案依赖于使用无类型(隐式类型)或显式类型参数,可以接受as-is[object][object]$null

function f {
    param (
        [AllowNull()] # Explicitly allow passing $null.
                      # Note: Strictly speaking only necessary with [Parameter(Mandatory=$True)]
        $x # Leave the parameter untyped (or use [object]) so as to retain $null as-is
    )

    # Convert $x to a type-constrained [string] variable *now*:
    if ($null -eq $x) {
        # Make $x contain $null, despite being [string]-typed
        [string] $x = [NullString]::Value
    } else {
        # Simply convert any other type to a string.
        [string] $x = $x
    }

    # $x is now a bona fide [string] variable that can be used
    # as such even in .NET method calls.

    return $x
}
Run Code Online (Sandbox Code Playgroud)

这有点麻烦,但允许调用者直接传递$null(或任何字符串,或将转换为字符串的任何其他实例的类型)。

一个小小的缺点是,这种方法不允许您通过由参数的特定类型选择的不同参数集在同一位置定义位置参数。


最后,值得一提的是,如果足以检测何时省略(非强制)参数,您可以检查$PSBoundParameters

function f {
    param (
        [string] $x
    )

    if ($PSBoundParameters.ContainsKey('x')) { # Was a value passed to parameter -x?
        "-x argument was passed: $x"
    } else {
        "no -x argument passed."
    }
}
Run Code Online (Sandbox Code Playgroud)

如前所述,这仅适用于省略情况(因此根本不适用于强制参数)。如果您传递$null,则通常会转换为'',并且您将无法区分传递$null''
(尽管如果您添加了上述解决方法/等待错误修复,您可以再次传递[NullString]::Value有效 pass $null,甚至用作[NullString]::Value参数默认值。)