当绑定到参数时,如何防止字符串参数从null变为空? [英] How can I prevent a string argument changing from null to empty when bound to a parameter?

查看:108
本文介绍了当绑定到参数时,如何防止字符串参数从null变为空?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

考虑以下代码:

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

$r = f -x $null

return时,

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

$null is converted to [string]::Empty by the time return is reached. $null is different from [string]::Empty and I'd like to preserve this distinction. I'd also prefer to keep $x as type [string] because $x only has meaning as a string and the interface is used elsewhere.

  1. 如何使$x通过$null后以$null的形式出现?
  2. 还有其他方法可以告诉我$x是从f内部传递的$null 不是 [string]::Empty吗?
  1. How can I make $x come out as $null when it is passed $null?
  2. Is there some other way I can tell that $x was passed $null not [string]::Empty from inside f?


更新1

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

What I am trying to do works for other types. Here is the same concept for [int]:

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

$r = f -x $null

在这种情况下,$r确实是$null. $x可以是$null[int],但不能有别的.我不得不允许任何对象,以便我可以传递$null[int].

In that case $r is indeed $null. $x can be either $null or [int] but nothing else. It seems strange to me to have to allow any object just so I can pass a $null or [int].

[System.Nullable[string]]会产生错误,归结为[System.Nullable[T]],要求[T]是值类型. [string]是引用类型,因此无效.

[System.Nullable[string]] produces an error that boils down to [System.Nullable[T]] requires that [T] is a value type. [string] is a reference type, so that doesn't work.

更新2

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

It seems to be possible to pass $null without causing conversion to a parameter of any type except [string]. I've tested the following:

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.我尚未找到唯一参数类型,而无需转换即可将$null传递给[string].

Passing $null to any of these functions outputs $null. The only parameter type I haven't found a way to which to pass $null without conversion is [string].

更新3

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

PowerShell's behavior in this regard is also inconsistent with C#. The corresponding function in C# is as follows:

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

呼叫f(null)会返回null.

更新4

显然[NullString]::Value旨在解决此问题.我似乎正在努力将null传递给 C#API中的参数.但是,在PowerShell中,[NullString]::Value转换为与$null相同的[string]::empty.考虑以下代码:

Apparently [NullString]::Value was intended to address this problem. I seems to work to pass null to string parameters in C# APIs. However, [NullString]::Value gets converted to [string]::empty in PowerShell the same as $null. Consider the following code:

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

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

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

Executing that code outputs String. $r is [string]::Empty despite that [NullString]::Value was passed to $x.

更新5

PowerShell团队已表明这是设计使然:

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

This is by design and ... changing the behavior would be a massive breaking change.

该主题涉及有关其背后原因的有趣讨论.我怀疑在做出决定时,这种行为的某些后果并没有被理解,因为该行为直接违反了,其部分内容如下:

That thread involved an interesting discussion about the reasoning behind it. I suspect that some of ramifications of this behavior were not understood when the decision was made as the behavior directly contravenes PowerShell cmdlet "Strongly Encouraged Design Guideline" SD03 which reads in part as follows:

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

If your parameter needs to differentiate between 3 values: $true, $false and "unspecified", then define a parameter of type Nullable. The need for a 3rd, "unspecified" value typically occurs when the cmdlet can modify a Boolean property of an object. In this case "unspecified" means to not change the current value of the property.

推荐答案

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

To summarize and complement the information from the question, answers, and comments:

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

  • PowerShell converts $null to '' (the empty string) when it is assigned to [string]-typed [parameter] variables, and parameter variables also default to ''.

  • The only exception is the use of uninitialized [string] properties in PSv5+ custom classes, as alxr9 (the OP) points out: class c { [string] $x }; $null -eq ([c]::new()).x indeed yields $True implying that property .x contains $null. However, this exception is likely accidental and probably a bug, given that when you initialize the property with $null or assign $null to it later, the conversion to '' again kicks in; similarly, using return $null from a [string]-typed method outputs ''.

除例外情况外,PowerShell的行为不同于 C#字符串变量/参数,您可以直接向其分配/传递null,并且在某些情况下默认为null. string是.NET 引用类型,并且此行为适用于所有引用类型.
(由于引用类型实例可以固有地包含null,因此不需要通过System.Nullable`1进行单独的可空包装,这实际上不受支持(仅适用于 value类型).)

The exception aside, PowerShell's behavior differs from C# string variables / parameter, to which you can assign / pass null directly, and which default to null in certain contexts. string is a .NET reference type, and this behavior applies to all reference types.
(Since reference type instances can inherently contain null, there is no need for a separate nullable wrapper via System.Nullable`1, which is indeed not supported (it works for value types only).)

正如问题中指出的那样,PowerShell脱离C#的行为是出于(历史性)设计,现在进行更改为时已晚.

As noted in the question, PowerShell's departure from C#'s behavior is by (historical) design, and it's too late to change it.

[NullString]::Value,以允许将null传递给 .NET方法的string参数-并且在纯PowerShell代码中使用时,没有明确劝阻或为了防止这种情况发生,更新4中的意外行为以及PowerShell核心团队成员的评论(请参阅下文)表明,这种使用是意料之外的.

[NullString]::Value was introduced in v3 specifically to allow passing null to string parameters of .NET methods - and while use in pure PowerShell code wasn't explicitly discouraged or prevented, the unexpected behavior in update 4 and the comments by a core PowerShell team member (see below) suggest that such uses weren't anticipated.

但是,由于 PetSerAl 的侦查,因此有一种解决方法来制作代码来自更新4的工作:

However, thanks to sleuthing by PetSerAl, there is a workaround to make the code from update 4 work:

function f {
    param (
        [string] $x
    )
    if ($False) { Remove-Variable } # Workaround
    return $x
}

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

最有可能是此优化错误,这是必需的-晦涩的-解决方法.

It is most likely this optimization bug that necessitates this - obscure - workaround.

请注意,将[NullString]::Value分配/传递给[string]类型的[parameter]变量时,它会立即 转换为$null(对于 parameter 变量,仅当错误已修复或有适当的解决方法时). 但是,一旦$null已成功以这种方式存储在变量中,则显然可以这样进行传递(再次,只有在错误得到修复或适当的解决方法的情况下).

Note that when assigning / passing [NullString]::Value to a [string]-typed [parameter] variable, it is instantly converted to $null (in the case of a parameter variable, only if the bug gets fixed or with the workaround in place). However, once $null has been successfully stored in the variable this way, it can apparently be passed around as such (again, only if the bug gets fixed or with the workaround in place).

注意事项:修复上述优化错误将有助于在update 4场景中进行,但鉴于[NullString]::Value的使用从未在调用上下文之外进行,因此可能会有其他陷阱. NET方法;引用 https://github.com/PowerShell/PowerShell/issues/4616#issuecomment-323597003 :

Caveat: Fixing the above-mentioned optimization bug would help in the update 4 scenario, but there may be other pitfalls, given that use of [NullString]::Value was never intended outside the context of calling .NET methods; to quote a core member of the PowerShell team from https://github.com/PowerShell/PowerShell/issues/4616#issuecomment-323597003:

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

Parameters to C# methods was the target scenario for [NullString]::Value, and I will say that might be the only reasonable scenario.


如果您不想依靠变通办法/等待修复,并且/或者不想让调用者不得不通过[NullString]::Value而不是$null来加重负担,则可以通过以下方式建立答案: Curios Jason Schnell ,这依赖于使用 untyped (隐式为[object]类型)或显式[object]类型的参数,可以照原样接受$null:


If you don't want to rely on the workaround / wait for the fix and/or don't want to burden the caller with having to pass [NullString]::Value instead of $null, you can build on the answers by Curios and Jason Schnell, which rely on using an untyped (implicitly [object]-typed) or explicitly [object]-typed parameter, which can accept $null as-is:

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(或任何字符串,或将转换为字符串的任何其他实例的类型).

It's somewhat cumbersome, but enables the caller to pass $null directly (or any string, or a type of any other instance that will be converted to a string).

不利的一面是,这种方法不允许您通过由参数的 类型选择的不同参数集在同一位置定义位置参数.

A slight down-side is that this approach doesn't allow you to define positional parameters in the same position via different parameter sets that are selected by the parameters' specific types.

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

Finally, it's worth mentioning that if it's sufficient to detect when a (non-mandatory) parameter was omitted, you can check $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."
    }
}

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

As stated, this only works for the omission case (and therefore doesn't work for mandatory parameters at all). If you pass $null, the usual conversion to '' kicks in, and you won't be able to distinguish between passing $null and ''.
(Though if you added the above workaround / waited for the bug fix, you could again pass [NullString]::Value to effectively pass $null, or even use [NullString]::Value as the parameter default value.)

这篇关于当绑定到参数时,如何防止字符串参数从null变为空?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆