Powershell:带有参数集的 C# 风格函数重载?
在 C# 中,函数重载历史上曾出现过如下情况,其中每个重载都会在更简单的签名之上添加一些参数:
public void Initialize(int version);
public void Initialize(int version, string workspaceName);
public void Initialize(int version, string workspaceName, Path workspaceRoot, bool force);
在 Powershell 中,诸如此类的重载是不可能的;但是,通过 System.Management.Automation.ParameterAttribute 的 ParameterSetName 属性提供了函数重载的某种近似值。这允许我们将参数声明为某些参数集的成员,这使我们能够有效地为我们的函数定义单独的签名。大多数 ParameterSetName 示例中都存在这种简单情况< /a> 是这样的:
function test-param
{
[CmdletBinding(DefaultParametersetName="p2")]
param(
[Parameter(ParameterSetName="p1",Position=0)]
[String]
$d,
[Parameter(ParameterSetName="p2", Position=0)]
[String]$i
)
switch ($PsCmdlet.ParameterSetName)
{
"p1" { Write-Host ([DateTime]$d); break}
"p2" { Write-Host ([INT]$i); break}
}
}
Get-help on this function 产生以下结果:
PS D:\.ws> get-help test-param
test-param [[-i] <String>] [-Verbose] [-Debug] [-ErrorAction <ActionPreference>] [-WarningAction <ActionPreference>] [-ErrorVariable <String>] [-WarningVariable <String>] [-OutVariable <String>] [-OutBuffer <Int32>]
test-param [[-d] <String>] [-Verbose] [-Debug] [-ErrorAction <ActionPreference>] [-WarningAction <ActionPreference>] [-ErrorVariable <String>] [-WarningVariable <String>] [-OutVariable <String>] [-OutBuffer <Int32>]
然而,对于参数集的复杂用法来说,这种基本类型的示例并不是令人难以置信,因为参数集是完全互斥的。但是如果我们希望某些参数成为所有参数集的成员怎么办?更有经验的用户可能会认为这是特殊“AllParameterSets”参数集的情况,根据 MSDN,如果未指定参数集,则该参数集是默认值。但是,请考虑以下事项:
PS D:\.ws> function test-param
{
[CmdletBinding(DefaultParametersetName="p2")]
param
(
[Parameter(ParameterSetName="p1",Position=0)]
[String]
$d,
[Parameter(ParameterSetName="p2", Position=0)]
[String]$i,
[Parameter(ParameterSetName="AllParameterSets")]
[String]$x
)
}
PS D:\.ws> get-help test-param
test-param [[-i] <String>] [-Verbose] [-Debug] [-ErrorAction <ActionPreference>] [-WarningAction <ActionPreference>] [-ErrorVariable <String>] [-WarningVariable <String>] [-OutVariable <String>] [-OutBuffer <Int32>]
test-param [[-d] <String>] [-Verbose] [-Debug] [-ErrorAction <ActionPreference>] [-WarningAction <ActionPreference>] [-ErrorVariable <String>] [-WarningVariable <String>] [-OutVariable <String>] [-OutBuffer <Int32>]
test-param [-x <String>] [-Verbose] [-Debug] [-ErrorAction <ActionPreference>] [-WarningAction <ActionPreference>] [-ErrorVariable <String>] [-WarningVariable <String>] [-OutVariable <String>] [-OutBuffer <Int32>]
显然,当将“AllParameterSets”指定为参数集时,它实际上并不像人们所期望的那样包含在所有参数集中。这可以通过省略声明来看出:
PS D:\.ws> function test-param
{
[CmdletBinding(DefaultParametersetName="p2")]
param
(
[Parameter(ParameterSetName="p1",Position=0)]
[String]
$d,
[Parameter(ParameterSetName="p2", Position=0)]
[String]$i,
[String]$x
)
}
PS D:\.ws> get-help test-param
test-param [[-i] <String>] [-x <String>] [-Verbose] [-Debug] [-ErrorAction <ActionPreference>] [-WarningAction <ActionPreference>] [-ErrorVariable <String>] [-WarningVariable <String>] [-OutVariable <String>] [-OutBuffer <Int32>]
test-param [[-d] <String>] [-x <String>] [-Verbose] [-Debug] [-ErrorAction <ActionPreference>] [-WarningAction <ActionPreference>] [-ErrorVariable <String>] [-WarningVariable <String>] [-OutVariable <String>] [-OutBuffer <Int32>]
现在,$x 参数出现在所有函数签名中,就像我们期望的那样。
最后是最复杂的情况。如果我们希望一个参数出现在某些签名中,而不是所有签名中,该怎么办?原始示例中给出的workspaceName 参数符合此描述。也许此解决方案的关键是认识到可以为 param 块中的每个参数声明多个 Parameter 属性。这允许我们为每个签名建立一个参数集,并用它所属的每个集合的 Parameter 属性来装饰每个参数。请考虑以下情况:
PS D:\.ws> function Initialize-Something
{
[CmdletBinding()]
param
(
[Parameter(ParameterSetName="version")]
[Parameter(ParameterSetName="workspaceName")]
[Parameter(ParameterSetName="createWorkspace")]
[int] $Version,
[Parameter(ParameterSetName="workspaceName")]
[Parameter(ParameterSetName="createWorkspace")]
[string] $WorkspaceName,
[Parameter(ParameterSetName="createWorkspace")]
[string] $WorkspaceRoot,
[Parameter(ParameterSetName="createWorkspace")]
[switch] $Force
)
}
PS D:\.ws> get-help initialize-something
Initialize-Something [-Version <Int32>] [-WorkspaceName <String>] [-WorkspaceRoot <String>] [-Force] [-Verbose] [-Debug] [-ErrorAction <ActionPreference>] [-WarningAction <ActionPreference>] [-ErrorVariable <String>] [-WarningVariable <String>] [-OutVariable <String>] [-OutBuffer <Int32>]
Initialize-Something [-Version <Int32>] [-WorkspaceName <String>] [-Verbose] [-Debug] [-ErrorAction <ActionPreference>] [-WarningAction <ActionPreference>] [-ErrorVariable <String>] [-WarningVariable <String>] [-OutVariable <String>] [-OutBuffer <Int32>]
Initialize-Something [-Version <Int32>] [-Verbose] [-Debug] [-ErrorAction <ActionPreference>] [-WarningAction <ActionPreference>] [-ErrorVariable <String>] [-WarningVariable <String>] [-OutVariable <String>] [-OutBuffer <Int32>]
PS D:\.ws> initialize-something -workspacename "test"
Initialize-Something : Parameter set cannot be resolved using the specified named parameters.
At line:1 char:21
+ initialize-something <<<< -workspacename "test"
+ CategoryInfo : InvalidArgument: (:) [Initialize-Something], ParameterBindingException
+ FullyQualifiedErrorId : AmbiguousParameterSet,Initialize-Something
Powershell 声称它无法确定参数集,很可能是因为 WorkspaceName 出现在多个签名中。根据记录,当仅指定 -Version 或除最复杂的签名之外的任何签名时,也会发生这种情况。这可以通过 [CmdletBinding(DefaultParameterSetName="version")] 或其他东西来缓解,但这不是一个合适的解决方案。
所以在这一切之后,我的问题是:我怎样才能获得我正在寻找的签名?我是否需要创建一组过于明确的开关(例如 -VersionMode、-WorkspaceNameMode、-CreateWorkspaceMode)来指定我希望它运行的模式,从本质上否定了参数集检测的优点?也许是模式枚举?可以通过使用 ParameterAttribute 的 Mandatory 和 Position 属性来实现这一点吗?
谢谢!
In C#, function overloading has historically appeared something like the following, where each overload adds some amount of parameters on top of the simpler signatures:
public void Initialize(int version);
public void Initialize(int version, string workspaceName);
public void Initialize(int version, string workspaceName, Path workspaceRoot, bool force);
In Powershell, overloads such as these are not possible; however, some approximation of function overloading is offered through System.Management.Automation.ParameterAttribute's ParameterSetName property. This allows us to declare parameters as members of certain parameter sets, which allows us to effectively define separate signatures for our functions. The simplistic case found in most ParameterSetName examples is something like this:
function test-param
{
[CmdletBinding(DefaultParametersetName="p2")]
param(
[Parameter(ParameterSetName="p1",Position=0)]
[String]
$d,
[Parameter(ParameterSetName="p2", Position=0)]
[String]$i
)
switch ($PsCmdlet.ParameterSetName)
{
"p1" { Write-Host ([DateTime]$d); break}
"p2" { Write-Host ([INT]$i); break}
}
}
Get-help on this function yields the following:
PS D:\.ws> get-help test-param
test-param [[-i] <String>] [-Verbose] [-Debug] [-ErrorAction <ActionPreference>] [-WarningAction <ActionPreference>] [-ErrorVariable <String>] [-WarningVariable <String>] [-OutVariable <String>] [-OutBuffer <Int32>]
test-param [[-d] <String>] [-Verbose] [-Debug] [-ErrorAction <ActionPreference>] [-WarningAction <ActionPreference>] [-ErrorVariable <String>] [-WarningVariable <String>] [-OutVariable <String>] [-OutBuffer <Int32>]
This basic type of example isn't incredibly for complex usages of parameter sets, however, because the parameter sets are entirely mutually exclusive. But what if we want some parameters to be members of all parameter sets? More experienced users might suggest that this is a case for the special "AllParameterSets" parameter set, which according to MSDN is the default if a parameter set is not specified. However, consider the following:
PS D:\.ws> function test-param
{
[CmdletBinding(DefaultParametersetName="p2")]
param
(
[Parameter(ParameterSetName="p1",Position=0)]
[String]
$d,
[Parameter(ParameterSetName="p2", Position=0)]
[String]$i,
[Parameter(ParameterSetName="AllParameterSets")]
[String]$x
)
}
PS D:\.ws> get-help test-param
test-param [[-i] <String>] [-Verbose] [-Debug] [-ErrorAction <ActionPreference>] [-WarningAction <ActionPreference>] [-ErrorVariable <String>] [-WarningVariable <String>] [-OutVariable <String>] [-OutBuffer <Int32>]
test-param [[-d] <String>] [-Verbose] [-Debug] [-ErrorAction <ActionPreference>] [-WarningAction <ActionPreference>] [-ErrorVariable <String>] [-WarningVariable <String>] [-OutVariable <String>] [-OutBuffer <Int32>]
test-param [-x <String>] [-Verbose] [-Debug] [-ErrorAction <ActionPreference>] [-WarningAction <ActionPreference>] [-ErrorVariable <String>] [-WarningVariable <String>] [-OutVariable <String>] [-OutBuffer <Int32>]
Apparently when "AllParameterSets" is specified as the parameter set, it is in fact not included in all parameter sets as one might expect. This can be seen by omitting the declaration:
PS D:\.ws> function test-param
{
[CmdletBinding(DefaultParametersetName="p2")]
param
(
[Parameter(ParameterSetName="p1",Position=0)]
[String]
$d,
[Parameter(ParameterSetName="p2", Position=0)]
[String]$i,
[String]$x
)
}
PS D:\.ws> get-help test-param
test-param [[-i] <String>] [-x <String>] [-Verbose] [-Debug] [-ErrorAction <ActionPreference>] [-WarningAction <ActionPreference>] [-ErrorVariable <String>] [-WarningVariable <String>] [-OutVariable <String>] [-OutBuffer <Int32>]
test-param [[-d] <String>] [-x <String>] [-Verbose] [-Debug] [-ErrorAction <ActionPreference>] [-WarningAction <ActionPreference>] [-ErrorVariable <String>] [-WarningVariable <String>] [-OutVariable <String>] [-OutBuffer <Int32>]
Now, the $x parameter appears in all function signatures like we'd expect.
Finally, the most complex case. What if we want one parameter to appear in some, but not all signatures? The workspaceName parameter given in the original example fits this description. Perhaps a key to this solution is realizing that multiple Parameter properties can be declared for each parameter in the param block. This allows us to establish a parameter set for each signature, and decorate each parameter with a Parameter attribute for each set it belongs to. Consider the following:
PS D:\.ws> function Initialize-Something
{
[CmdletBinding()]
param
(
[Parameter(ParameterSetName="version")]
[Parameter(ParameterSetName="workspaceName")]
[Parameter(ParameterSetName="createWorkspace")]
[int] $Version,
[Parameter(ParameterSetName="workspaceName")]
[Parameter(ParameterSetName="createWorkspace")]
[string] $WorkspaceName,
[Parameter(ParameterSetName="createWorkspace")]
[string] $WorkspaceRoot,
[Parameter(ParameterSetName="createWorkspace")]
[switch] $Force
)
}
PS D:\.ws> get-help initialize-something
Initialize-Something [-Version <Int32>] [-WorkspaceName <String>] [-WorkspaceRoot <String>] [-Force] [-Verbose] [-Debug] [-ErrorAction <ActionPreference>] [-WarningAction <ActionPreference>] [-ErrorVariable <String>] [-WarningVariable <String>] [-OutVariable <String>] [-OutBuffer <Int32>]
Initialize-Something [-Version <Int32>] [-WorkspaceName <String>] [-Verbose] [-Debug] [-ErrorAction <ActionPreference>] [-WarningAction <ActionPreference>] [-ErrorVariable <String>] [-WarningVariable <String>] [-OutVariable <String>] [-OutBuffer <Int32>]
Initialize-Something [-Version <Int32>] [-Verbose] [-Debug] [-ErrorAction <ActionPreference>] [-WarningAction <ActionPreference>] [-ErrorVariable <String>] [-WarningVariable <String>] [-OutVariable <String>] [-OutBuffer <Int32>]
PS D:\.ws> initialize-something -workspacename "test"
Initialize-Something : Parameter set cannot be resolved using the specified named parameters.
At line:1 char:21
+ initialize-something <<<< -workspacename "test"
+ CategoryInfo : InvalidArgument: (:) [Initialize-Something], ParameterBindingException
+ FullyQualifiedErrorId : AmbiguousParameterSet,Initialize-Something
Powershell claims it can't determine a parameter set, more than likely because WorkspaceName appears in multiple signatures. For the record, this also occurs when specifying only the -Version, or for that matter any signature other than the most complex. This can be alleviated somewhat with the [CmdletBinding(DefaultParameterSetName="version")] or something, but it's not a proper solution.
So after all this, my question: How can I achieve the kind of signatures I'm looking for? Do I need to create a set of overly-explicit switches such as -VersionMode, -WorkspaceNameMode, -CreateWorkspaceMode to specify which mode I want it to run, essentially negating the niceness of parameter set detection? Perhaps a mode enum? Can this be done through some elegance using ParameterAttribute's Mandatory and Position properties?
Thanks!
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(2)
如果您尝试模拟 C# 函数重载,最简单的方法是简单地允许“可选”参数,例如:
调用者未指定的参数将默认为 $null(开关将默认为 $false)。您还可以为参数提供默认值,如下所示:
If you are trying to emulate C# function overloads the easiest way is to simply allow for "optional" parameters e.g.:
The parameters not specified by the invoker will default to $null (the switch will default to $false). You can also provide default values for the parameters like so:
关于参数集,需要记住的两件重要事情是,Windows PowerShell 运行时仅对特定输入使用一个参数集,并且每个参数集必须至少有一个对该参数集唯一的参数。
在您的情况下,您需要指定第二个参数,例如
-WorkspaceRoot
,它将起作用:initialize-something -workspacename "test" -WorkspaceRoot "test2"
Two important things to remember about parameter sets is that the Windows PowerShell runtime uses only one parameter set for a particular input, and that each parameter set must have at least one parameter that is unique for that parameter set.
In your case you need to specify a second parameter like
-WorkspaceRoot
and it will work:initialize-something -workspacename "test" -WorkspaceRoot "test2"