在 Powershell 中使用 IEnumerable<string> 类型的参数调用 C# 方法

Calling a C# method with argument of type IEnumerable<string> in Powershell

我正在使用 PowerShell 的 Box.V2 SDK。 我正在调用此处记录的方法:

https://github.com/box/box-windows-sdk-v2/blob/master/Box.V2/Managers/BoxUsersManager.cs

这是我的代码:

# Load Assemblies
[Reflection.Assembly]::LoadFile("C:\Users\jfrank\Documents\BoxSDKV2\System.IdentityModel.Tokens.Jwt.5.1.4\lib\net45\System.IdentityModel.Tokens.Jwt.dll")
[Reflection.Assembly]::LoadFile("C:\Users\jfrank\Documents\BoxSDKV2\BouncyCastle.1.8.1\lib\BouncyCastle.Crypto.dll")
[Reflection.Assembly]::LoadFile("C:\Users\jfrank\Documents\BoxSDKV2\Box.V2.3.3.0\lib\net45\Box.V2.dll")
[Reflection.Assembly]::LoadFile("C:\Users\jfrank\Documents\BoxSDKV2\Microsoft.IdentityModel.Logging.1.1.4\lib\net45\Microsoft.IdentityModel.Logging.dll")
[Reflection.Assembly]::LoadFile("C:\Users\jfrank\Documents\BoxSDKV2\Microsoft.IdentityModel.Tokens.5.1.4\lib\net45\Microsoft.IdentityModel.Tokens.dll")
[Reflection.Assembly]::LoadFile("C:\Users\jfrank\Documents\BoxSDKV2\Newtonsoft.Json.10.0.3\lib\net45\Newtonsoft.Json.dll")
[Reflection.Assembly]::LoadFile("C:\Users\jfrank\Documents\BoxSDKV2\Newtonsoft.Json.9.0.1\lib\net45\Newtonsoft.Json.dll")

# Configure
$jsonConfig = Get-content "C:\Users\jfrank\Documents\WindowsPowerShell\Scripts\Box\config.json" | ConvertFrom-Json
$boxConfig = New-Object Box.V2.config.BoxConfig (($jsonConfig.boxAppSettings).clientID, ($jsonConfig.boxAppSettings).clientSecret, $jsonConfig.enterpriseID, (($jsonConfig.boxAppSettings).appAuth).privateKey, (($jsonConfig.boxAppSettings).appAuth).passphrase, (($jsonConfig.boxAppSettings).appAuth).publicKeyID)
$boxJWT = New-Object Box.V2.JWTAuth.BoxJWTAuth ($boxConfig)

# Authenticate
$adminToken = $boxJWT.AdminToken()
$adminClient = $boxJWT.AdminClient($adminToken)

# Get Users
$enterpriseUsers = $adminClient.UsersManager.GetEnterpriseUsersAsync().Result

# Get Admin by name
Foreach ($entry in $enterpriseUsers.Entries) {
    if ($entry.Name -eq "Johannes Frank") {
            $admin = $entry
        }
    }

# Get User
$userToken = $boxJWT.UserToken($admin.Id)
$userClient = $boxJWT.UserClient($userToken, $admin.Id)


$fields = [System.Collections.Generic.IEnumerable`1[System.String]]"role, enterprise"

$userClient.UsersManager.GetCurrentUserInformationAsync($fields).Result | Format-List

没有参数 $fields 最后一行完美运行。但是带参数调用很乏味。

除了其他尝试之外,我目前收到以下错误消息:

GAC    Version        Location                                                                                                             
---    -------        --------                                                                                                             
False  v4.0.30319     C:\Users\jfrank\Documents\BoxSDKV2\System.IdentityModel.Tokens.Jwt.5.1.4\lib\net45\System.IdentityModel.Tokens.Jwt...
False  v1.1.4322      C:\Users\jfrank\Documents\BoxSDKV2\BouncyCastle.1.8.1\lib\BouncyCastle.Crypto.dll                                    
False  v4.0.30319     C:\Users\jfrank\Documents\BoxSDKV2\Box.V2.3.3.0\lib\net45\Box.V2.dll                                                 
False  v4.0.30319     C:\Users\jfrank\Documents\BoxSDKV2\Microsoft.IdentityModel.Logging.1.1.4\lib\net45\Microsoft.IdentityModel.Logging...
False  v4.0.30319     C:\Users\jfrank\Documents\BoxSDKV2\Microsoft.IdentityModel.Tokens.5.1.4\lib\net45\Microsoft.IdentityModel.Tokens.dll 
False  v4.0.30319     C:\Users\jfrank\Documents\BoxSDKV2\Newtonsoft.Json.10.0.3\lib\net45\Newtonsoft.Json.dll                              
False  v4.0.30319     C:\Users\jfrank\Documents\BoxSDKV2\Newtonsoft.Json.9.0.1\lib\net45\Newtonsoft.Json.dll                               
Cannot convert the "role, enterprise" value of type "System.String" to type "System.Collections.Generic.IEnumerable`1[System.String]".
At C:\Users\jfrank\Documents\WindowsPowerShell\Scripts\Box\BoxJWTAuth.ps1:34 char:1
+ $fields = [System.Collections.Generic.IEnumerable`1[System.String]]"r ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : InvalidArgument: (:) [], RuntimeException
    + FullyQualifiedErrorId : ConvertToFinalInvalidCastException

Cannot convert argument "fields", with value: "Box.V2.Models.BoxUser", for "GetCurrentUserInformationAsync" to type 
"System.Collections.Generic.IEnumerable`1[System.String]": "Cannot convert the "Box.V2.Models.BoxUser" value of type 
"Box.V2.Models.BoxUser" to type "System.Collections.Generic.IEnumerable`1[System.String]"."
At C:\Users\jfrank\Documents\WindowsPowerShell\Scripts\Box\BoxJWTAuth.ps1:36 char:1
+ $userClient.UsersManager.GetCurrentUserInformationAsync($fields).Resu ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : NotSpecified: (:) [], MethodException
    + FullyQualifiedErrorId : MethodArgumentConversionInvalidCastArgument

谁能告诉我怎么称呼这个

public async Task<BoxUser> GetCurrentUserInformationAsync(IEnumerable<string> fields = null)

方法及其参数字段?

编辑:您会在下面找到答案:[string[]] 用作正确的类型转换。还有其他两种方法。

个人评价: 但我现在真的更看重Python。 Python 不是 GW Basic。我在那里也有类型安全。 花费(浪费)时间将价值转化为论点。我得到了什么样的"security"补偿,还没有完全抓住"Generic Types"的原因?无论如何,我要进行 运行 单元测试。如果有人喜欢评论一下这个系统的优点,我很乐意学习

&{ # About this http://community.idera.com/powershell/powertips/b/tips/posts/using-custom-scopes
  $OFS=', ' # About $OFS https://blogs.msdn.microsoft.com/powershell/2006/07/15/psmdtagfaq-what-is-ofs/
  $PSVersionTable # The Powershell version and not only 
# The way number one to takle the problem
  $fields = [activator]::createinstance([System.Collections.Generic.List``1].makegenerictype([System.String]))
  $fields.AddRange([string[]]("role", "enterprise"))
# The next three lines are needed just to show the result
  $fields.gettype().fullname
  $fields.gettype().getInterfaces() | ?{$_.Name.startswith('IEnumerable') -and $_.IsGenericType} | Select -ExpandProperty FullName
  """${fields}"""
  rv fields # rv is the alias of Remove-Variable cmdlet
# The way number two
  $fields = [System.Collections.Generic.List[System.String]][string[]]("role", "enterprise")
  $fields.gettype().fullname
  $fields.gettype().getInterfaces() | ?{$_.Name.startswith('IEnumerable') -and $_.IsGenericType} | Select -ExpandProperty FullName
  """${fields}"""
  rv fields
# The way number three
  $fields = [string[]]("role", "enterprise")
  $fields.gettype().fullname
  $fields.gettype().getInterfaces() | ?{$_.Name.startswith('IEnumerable') -and $_.IsGenericType} | Select -ExpandProperty FullName
  """${fields}"""
}
Name                           Value
CLRVersion                     2.0.50727.8762
BuildVersion                   6.1.7601.17514                                                                                                                                                                                          
PSVersion                      2.0
WSManStackVersion              2.0
PSCompatibleVersions           {1.0, 2.0}                                                                                                                                                                                                                          
SerializationVersion           1.1.0.1
PSRemotingProtocolVersion      2.1                                                                                                                                                                                                                                 
System.Collections.Generic.List`1[[System.String, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]]
System.Collections.Generic.IEnumerable`1[[System.String, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]]
"role, enterprise"
System.Collections.Generic.List`1[[System.String, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]]
System.Collections.Generic.IEnumerable`1[[System.String, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]]
"role, enterprise"
System.String[]
System.Collections.Generic.IEnumerable`1[[System.String, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]]
"role, enterprise"

I really cannot see how these questions are related. I had read this answer before and my initial try came from that answer. I spent (possibly wasted) hours in tries to do that single typecast. In python I would have just assigned and had the right type right away. Can you explain why these questions are related and what do I gain by this kind of type "security"?

你没有获得任何安全感。您正在使用原始泛型 class 创建非常特定类型的对象。需要这样做的情况非常少,这就是为什么这并不容易。

你看,我从来没有用过box-windows-sdk-v2。但是,您的代码告诉我我需要加载七个程序集并且有一个现有的 JSON 配置文件来实例化 Box.V2.config.BoxConfig。我什至没有 Box 用户帐户。请理解,有人为了测试您的代码而进行设置是不合理的。

我看到的是这条错误信息:

"Cannot convert the "Box.V2.Models.BoxUser" value of type "Box.V2.Models.BoxUser" to type "System.Collections.Generic.IEnumerable`1[System.String]"

现在,你的问题和你提问的方式让我相信你理解 IEnumerable<T> 是什么。但是,现在我认为您没有这样做,所以您还没有尝试过我已经尝试过的东西。

@Andrei 的回答是构建您自己的自定义 class 来实现 IEnumerable,而不是使用任何已经这样做的现有 class。但是,根据您提出问题的方式,这就是您的要求

所以,让我们从头开始:

IList<T>ICollection<T>IEnumerable<T>generic interfaces to objects. They're primitive types in C# that allow you to construct custom collection types. They're also implemented by the built in generic collection types like List<T> and Stack<T> and Dictionary<TKey, TValue>. This means that if you need to write a C# method that needs to enumerate a collection, you can write it using the the IEnumerable<T> type and it will be able to enumerate any collections of objects as long as that collection implements the IEnumerable<T> interface. Even System.Array was extended to include these types,有一些特定的注意事项,某些方法会失败:

Single-dimensional arrays implement the System.Collections.Generic.IList, System.Collections.Generic.ICollection, System.Collections.Generic.IEnumerable, System.Collections.Generic.IReadOnlyList and System.Collections.Generic.IReadOnlyCollection generic interfaces. The implementations are provided to arrays at run time, and as a result, the generic interfaces do not appear in the declaration syntax for the Array class. In addition, there are no reference topics for interface members that are accessible only by casting an array to the generic interface type (explicit interface implementations). The key thing to be aware of when you cast an array to one of these interfaces is that members which add, insert, or remove elements throw NotSupportedException.

那么,您的错误信息是,"I need an object that is a collection of System.Strings that implements the IEnumerable<T> interface."

这意味着我假设您最初尝试过其中之一:

$fields = "role", "enterprise"  # Creates a [System.Object[]], so should error

$fields = @("role", "enterprise")  # Creates a [System.Object[]], so should error

PowerShell 中的默认数组是 [System.Object[]] 类型,因为 PowerShell 试图尽可能通用。 PowerShell 的命令都接受该类型。但是,当您从已加载的库调用对象方法时,您实际上是在 C# 领域工作,而不是在 PowerShell 中工作。您必须创建符合 C# 要求的对象。这就是为什么感觉就像你在迷雾中写作,系统没有说清楚它想要什么。您实际上是在使用 PowerShell(尝试不静态类型化)编写 C# 代码(尝试静态类型化)。

这里的问题是 .Net Framework 中没有内置方法自动将 [Object[]] 转换为实现 [IEnumerable[String]] 的对象。就像你不能说 'Hello' + 1 + 'World',在 Python 中,语言不知道如何为你进行转换。

如果以上任何一项都不起作用,您应该尝试过其中任何一项:

$fields = [System.String[]]("role", "enterprise")  # Creates a [System.String[]], so may work
# [System.String[]] implements [IEnumerable[String]], but not completely

$fields = [System.Collections.Generic.List[System.String]]("role", "enterprise")
# Creates a [List[String]], and should work because that implements [IEnumerable[String]]

$fields = New-Object -TypeName "System.Collections.Generic.List[System.String]"
$fields.Add('role')
$fields.Add('enterprise')
# Also creates a [List[String]]

$fields = New-Object -TypeName "System.Collections.Generic.Stack[System.String]"
$fields.Push('role')
$fields.Push('enterprise')
# Creates a [Stack[String]], and should work because that also implements [IEnumerable[String]]

$fields = New-Object -TypeName "System.Collections.Generic.HashSet[System.String]"
[void]$fields.Add('role')
[void]$fields.Add('enterprise')

# See also System.Collections.Generic.Queue, System.Collections.Generic.SortedSet, System.Collections.Generic.LinkedList, etc.

但是我不知道Box.V2.Models.BoxUser是做什么的,我也不知道它对对象到底有多挑剔。 可能 您实际上需要原始泛型,这就是我的假设,因为您在尝试创建它时遇到了很多麻烦。这显然也是这里的其他答案所相信的。

至于,"Why is PowerShell so complicated when Python is so simple?":

好吧,您不是在此脚本中编写 PowerShell。你不。 PowerShell 看起来像这样:

Get-ChildItem *.pdf -File | Where-Object {
    $_.LastWriteTime -lt $CutoffDate
} | Move-Item -Destination C:\archive\ -Verbose

您在这里所做的是使用 PowerShell 编写解释性 C#。您可以这样做,因为 PowerShell 允许您完全访问 .Net Framework,但使用第三方库是一个高级 PowerShell 主题。它根本不应该是您编写的第一个脚本。大约有 30% 的 PowerShell 和 70% 的 C#,您确实需要至少对两者有一个有效的理解,因为一旦您开始调用第三方库,许多使 PowerShell 快速且易于使用的约定就根本不起作用了。

您必须明白,使用 PowerShell 来执行此操作是可行的,但它不是任何库的作者希望您使用的内容。 PowerShell 不是 C#。您会遇到一些奇怪的库,它们没有以 PowerShell 能够很好地配合使用的方式实现事物,因为通过使用 PowerShell 来实现它,您已经自动将自己置于使用该库的大约 1% 的开发人员中。 PowerShell 的约定不会 100% 与 C# 和第三方库的约定一致。