【问题标题】:How can I prevent a string argument changing from null to empty when bound to a parameter?绑定到参数时,如何防止字符串参数从 null 变为空?
【发布时间】:2017-08-16 17:56:07
【问题描述】:

考虑以下代码:

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

$r = f -x $null

$null 在达到return 时转换为[string]::Empty$null[string]::Empty 不同,我想保留这种区别。我还希望将$x 保留为[string] 类型,因为$x 仅作为字符串具有意义,并且该接口在其他地方使用。

  1. 如何让$x 在通过$null 时输出为$null
  2. 还有其他方法可以告诉我$x 是从f 内部传递的$null 不是 [string]::Empty

更新 1

我正在尝试做的事情适用于其他类型。这是[int] 的相同概念:

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

$r = f -x $null

在这种情况下,$r 确实是$null$x 可以是 $null[int],但仅此而已。必须允许 any 对象对我来说似乎很奇怪,这样我才能传递 $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 }

$null 传递给这些函数中的任何一个都会输出$null。 only 参数类型我还没有找到传递$null 而不进行转换的方法是[string]


更新 3

PowerShell 在这方面的行为也与 C# 不一致。 C#中对应的函数如下:

public string f(string x)
{
    return x;
}

调用f(null) 返回null


更新 4

Apparently [NullString]::Value was intended to address this problem. 我似乎正在努力将 C# API 中的 null 传递给 string 参数。但是,[NullString]::Value 在 PowerShell 中转换为 [string]::empty,与 $null 相同。考虑以下代码:

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

$r = f -x ([NullString]::Value)
$r.GetType().Name

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


更新 5

The PowerShell team has indicated that this was by design:

这是设计使然,并且...改变行为将是一个巨大的突破性变化。

该线程涉及有关其背后原因的有趣讨论。我怀疑在做出决定时并未理解此行为的某些后果,因为该行为直接违反了PowerShell cmdlet "Strongly Encouraged Design Guideline" SD03,其中部分内容如下:

如果您的参数需要区分 3 个值:$true、$false 和“未指定”,则定义一个 Nullable 类型的参数。当 cmdlet 可以修改对象的布尔属性时,通常需要第三个“未指定”值。在这种情况下,“未指定”意味着不更改属性的当前值。

【问题讨论】:

  • 您正在将参数转换为一个字符串,其中 [string] 在 $x 之前
  • 没错。如果您不希望将某些内容转换为字符串:不要将其转换为字符串。
  • Nullable<T> doesn't work for strings,因为类型本身已经可以为空(原则上)。您面临的问题是 PowerShell 在将 $null 值传递给参数时将其转换为字符串,从而自动将该值转换为空字符串。
  • @AnsgarWiechers 我同意。当[AllowNull()][string] 都存在时,转换为[string]::empty 似乎是一种奇怪的行为。
  • @JasonSnell “字符串不能为空,只能为空。” 我不认为这是一个准确的概括。例如,对于班级成员来说,情况并非如此。如果实例化class c { [string]$x },则x$null 而不是[string]::Empty

标签: string powershell parameters null


【解决方案1】:

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

tl;dr

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


  • PowerShell 在将$null 分配给[string] 类型的[parameter] 变量时将其转换为''(空字符串),并且parameter 变量也是default ''

    • 唯一的例外是在 PSv5+ 自定义类中使用 uninitialized [string] properties,正如 alxr9(OP)指出的那样: class c { [string] $x }; $null -eq ([c]::new()).x 确实产生了$True,这意味着属性.x 包含$null。但是,此异常可能是意外probably a bug,因为当您使用$null 初始化属性或稍后将$null 分配给它时,到'' 的转换再次开始;同样,使用[string]-typed method 中的return $null 输出''

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

  • 如问题(更新 5)中所述,PowerShell 与 C# 行为的不同是(设计,它是 changing it is not an option 仅出于向后兼容性的原因。

  • [NullString]::Value 在 v3 中引入,专门用于允许将 .NET 方法的null 传递给string 参数 - 而同时并未明确阻止或阻止在纯 PowerShell 代码中使用,更新 4 中的意外行为和核心 PowerShell 团队成员(见下文)的 cmets 表明此类使用不是预期的。

    • 警告:虽然可以在纯 PowerShell 代码中使用[NullString]::Value,但可能存在下面讨论的陷阱之外的陷阱,因为 strong>[NullString]::Value 的使用从未打算在调用 .NET 方法的上下文之外使用;致quote a core member of the PowerShell team

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

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

如果尽管有上述建议,您确实需要一个 [string] 参数变量,您可以通过 $null 将其传递给 [NullString]::Value,如您问题中的更新 4 所示,有一个- 晦涩 - workaround 用于 the optimization bug,由于 PetSerAl 的侦查,阻止您的代码工作:

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

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


如果您不想依赖解决方法/等待修复和/或不想给调用者带来负担,必须传递 [NullString]::Value 而不是 $null,您可以通过以下方式构建答案CuriosJason Schnell,它们依赖于使用 untyped(隐式 [object]-typed)或显式 [object]-typed 参数,可以按原样接受$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
}

这有点麻烦,但允许调用者直接传递$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."
    }
}

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

【讨论】:

  • [NullString]::Value 成功了!我必须将真正的 NULL 传递给区分 NULL 和空字符串的外部函数。从来没有意识到 Powershell 会像这样搞砸字符串。
【解决方案2】:
function f {
    param (
        [AllowNull()]$x
    )
    return $x
}

$r = f -x $null

通过删除[string] 并使用[AllowNull()],上述函数现在将允许您传入空对象或空字符串。您可以使用带有 if 语句的 $x.GetType 来检查类型,并确定 $x 是 null 还是空字符串。

【讨论】:

  • 如果您想确保参数是$null 或字符串,您可以验证参数:[ValidateScript($_ -eq $null -or $_ -is [string])]。您还可以将非空参数值强制转换为正文中的字符串,但这可能会产生不希望的结果(例如,当对象的字符串表示是它的类型而不是它的值时)。
  • @AnsgarWiechers 您应该将该评论作为答案。这是迄今为止最有用的建议。
  • 不。这个答案已经解决了根本问题。我只是建议一些微调。
  • 当您将$null 传递给具有[ValidateScript()] 属性的参数时,FWIW PowerShell 会引发异常。看起来参数验证必须在正文中进行。
【解决方案3】:

默认情况下,[string] 将默认值分配为[string]::Empty,因此参数定义将在进入函数 f 时对其进行转换。
一个。您可以将参数更改为[object]$x

[object]$newparamnull -eq $null
[string]$newparamstring -eq [string]::Empty    

b.之前的更改将完成这项工作:

function f {
    param (
        [AllowNull()]
        [object]
        $x)
   if($x -eq $null) {
      write-output "null" 
   }
   elseif($x -eq [string]::empty){
      write-output "empty"
   } 
   else {"other"}
}

测试:

f -x $null
f -x [string]::empty
f -x "aaa"

【讨论】:

  • “默认情况下,[string] 将默认值分配为 [string]::Empty” 我认为这不是一个准确的概括。例如,对于类来说,情况并非如此。如果实例化class c { [string]$x },则x$null 而不是[string]::Empty
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-04-11
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多