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

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

问题描述

考虑以下代码:

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

$r = f -x $null

$null 在到达 return 时转换为 [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 已通过 $null not [string]::Empty 从内部 f?
  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] 但没有别的.对我来说,必须允许 any 对象只是为了传递 $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]code> 是一种值类型.[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.only 参数类型我还没有找到一种无需转换即可传递 $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 旨在解决这个问题.在 C# API 中将 null 传递给 string 参数.但是,[NullString]::Value 在 PowerShell 中被转换为 [string]::empty,与 $null 相同.考虑以下代码:

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.$r[string]::Empty 尽管 [NullString]::Value 被传递给 $x.

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.

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

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:

tl;dr:

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

It's best not to fight PowerShell's design of not allowing [string] variables to be $null, and to limit use of [NullString]::Value to calls to .NET methods.

  • PowerShell 将 $null 转换为 ''(空字符串),当它分配给 [string] 时-typed [parameter] 变量,parameter 变量也default''.

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

  • 唯一的例外是在 PSv5+ 自定义类中使用 未初始化 [string] 属性,正如 alxr9(OP)指出的那样: class c { [string] $x };$null -eq ([c]::new()).x 确实产生 $True 暗示属性 .x 包含 $null.但是,此异常很可能偶然并且可能是一个错误,假设当您使用 $null 初始化属性或稍后将 $null 分配给它时,将再次转换为 '';类似地,使用 return $null[string] 类型的 method 输出 ''.

  • 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 单独的可空包装器,这确实不受支持(它适用于 仅值类型).)

The exception aside, PowerShell's behavior differs from C# string variables / parameters, 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).)

如问题(更新 5)中所述,PowerShell 与 C# 的行为背离是 (design, and it's 仅出于向后兼容性的原因,不能更改它.

As noted in the question (update 5), PowerShell's departure from C#'s behavior is by (design, and it's changing it is not an option for reasons of backward compatibility alone.

[NullString]::Value 是在 v3 中引入的,专门用于允许将 null 传递给 string .NET 方法的参数 - 虽然没有明确阻止或阻止在纯 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.

  • Caveat: While it is possible to use [NullString]::Value in pure PowerShell code, there may be pitfalls beyond the one discussed below, 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:

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.

  • 一个解决方法将您的(参数)变量输入为[object]不对其进行类型约束strong> 完全一样,这也一样.这样的变量很乐意接受$null,但请注意,您可能必须对非$null 进行字符串化(转换为[string])自己赋值(尽管 PowerShell 在显式或隐式字符串上下文中自动为您执行此操作)- 请参阅下面的倒数第二个代码示例.
    • A workaround is to type your (parameter) variable as [object] or to not type-constrain it at all, which amounts to the same. Such variables happily accept $null, but note that you may have to stringify (convert to [string]) non-$null values yourself (although PowerShell does that for you automatically in explicit or implied string contexts) - see the penultimate code example below.
    • 如果,尽管有上述建议,您确实需要一个 [string] 参数变量,您可以通过 [NullString] 将 $null 传递给它::Value,就像您问题中的更新 4 一样,优化错误阻止您的代码工作,这要归功于 宠物服务:

      If, despite the advice above, you do need a [string] parameter variable that you can pass $null to via [NullString]::Value, as in update 4 in your question, there is an - obscure - workaround for the optimization bug that prevents your code from working, thanks to sleuthing by 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(在 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).

      如果您不想依赖解决方法/等待修复和/或不想给调用者带来负担,而必须传递 [NullString]::Value 而不是 $null,您可以通过 CuriosJason Schnell,依赖于使用无类型(隐式[object]类型)或显式[object]-typed 参数,可以按原样接受$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天全站免登陆