如何在Powershell中调用自定义类的通用静态方法?
鉴于以下课程:
public class Sample
{
public static string MyMethod<T>( string anArgument )
{
return string.Format( "Generic type is {0} with argument {1}", typeof(T), anArgument );
}
}
这被编译成一个程序集'Classes.dll'并加载到PowerShell中,如下所示:
Add-Type -Path "Classes.dll"
调用MyMethod方法的最简单方法是什么?
答案 0 :(得分:14)
正如@Athari所说,调用MyMethod的最简单方法是使用MakeGenericMethod。由于他实际上并没有说明如何做到这一点,因此这是一个经过验证的工作代码示例:
$obj = New-Object Sample
$obj.GetType().GetMethod("MyMethod").MakeGenericMethod([String]).Invoke($obj, "Test Message")
$obj.GetType().GetMethod("MyMethod").MakeGenericMethod([Double]).Invoke($obj, "Test Message")
带输出
Generic type is System.String with argument Test Message
Generic type is System.Double with argument Test Message
答案 1 :(得分:12)
您可以调用泛型方法,请参阅帖子Invoking Generic Methods on Non-Generic Classes in PowerShell。
这不是直截了当的,您需要使用MakeGenericMethod
函数。如果方法没有覆盖,则非常简单,如果方法没有,则会变得更加困难。
以防万一,从那里复制粘贴的代码:
## Invoke-GenericMethod.ps1
## Invoke a generic method on a non-generic type:
##
## Usage:
##
## ## Load the DLL that contains our class
## [Reflection.Assembly]::LoadFile("c:\temp\GenericClass.dll")
##
## ## Invoke a generic method on a non-generic instance
## $nonGenericClass = New-Object NonGenericClass
## Invoke-GenericMethod $nonGenericClass GenericMethod String "How are you?"
##
## ## Including one with multiple arguments
## Invoke-GenericMethod $nonGenericClass GenericMethod String ("How are you?",5)
##
## ## Ivoke a generic static method on a type
## Invoke-GenericMethod ([NonGenericClass]) GenericStaticMethod String "How are you?"
##
param(
$instance = $(throw "Please provide an instance on which to invoke the generic method"),
[string] $methodName = $(throw "Please provide a method name to invoke"),
[string[]] $typeParameters = $(throw "Please specify the type parameters"),
[object[]] $methodParameters = $(throw "Please specify the method parameters")
)
## Determine if the types in $set1 match the types in $set2, replacing generic
## parameters in $set1 with the types in $genericTypes
function ParameterTypesMatch([type[]] $set1, [type[]] $set2, [type[]] $genericTypes)
{
$typeReplacementIndex = 0
$currentTypeIndex = 0
## Exit if the set lengths are different
if($set1.Count -ne $set2.Count)
{
return $false
}
## Go through each of the types in the first set
foreach($type in $set1)
{
## If it is a generic parameter, then replace it with a type from
## the $genericTypes list
if($type.IsGenericParameter)
{
$type = $genericTypes[$typeReplacementIndex]
$typeReplacementIndex++
}
## Check that the current type (i.e.: the original type, or replacement
## generic type) matches the type from $set2
if($type -ne $set2[$currentTypeIndex])
{
return $false
}
$currentTypeIndex++
}
return $true
}
## Convert the type parameters into actual types
[type[]] $typedParameters = $typeParameters
## Determine the type that we will call the generic method on. Initially, assume
## that it is actually a type itself.
$type = $instance
## If it is not, then it is a real object, and we can call its GetType() method
if($instance -isnot "Type")
{
$type = $instance.GetType()
}
## Search for the method that:
## - has the same name
## - is public
## - is a generic method
## - has the same parameter types
foreach($method in $type.GetMethods())
{
# Write-Host $method.Name
if(($method.Name -eq $methodName) -and
($method.IsPublic) -and
($method.IsGenericMethod))
{
$parameterTypes = @($method.GetParameters() | % { $_.ParameterType })
$methodParameterTypes = @($methodParameters | % { $_.GetType() })
if(ParameterTypesMatch $parameterTypes $methodParameterTypes $typedParameters)
{
## Create a closed representation of it
$newMethod = $method.MakeGenericMethod($typedParameters)
## Invoke the method
$newMethod.Invoke($instance, $methodParameters)
return
}
}
}
## Return an error if we couldn't find that method
throw "Could not find method $methodName"
答案 2 :(得分:5)
这是PowerShell的限制,无法直接在PowerShell V1或V2 AFAIK中完成。
BTW你的通用方法并不是通用的。不应该是:
public static string MyMethod<T>(T anArgument)
{
return string.Format( "Generic type is {0} with argument {1}",
typeof(T), anArgument.ToString());
}
如果您拥有此代码并希望在PowerShell中使用它,请避免使用泛型方法或编写非泛型C#包装器方法。
答案 3 :(得分:2)
好消息是PowerShell v3在绑定泛型方法方面要好得多(并且通过它们来实现?)而且你通常不需要做任何特殊的事情,而是像普通方法一样调用它。我无法指定现在可以使用的所有条件,但根据我的经验,某些具有通用参数的情况仍然需要解决方法,即使在PowerShell v4中(可能是存在或重载或类似的东西)。
同样,我有时也会将泛型参数传递给方法...例如传递Func<T1, T2, TResult>
参数。
对我来说,一个解决方法比MakeGenericMethod简单得多,或者其他方法只是在我的脚本中直接放置一个快速的C#包装类,让C#整理出所有通用映射......
以下是包含Enumerable.Zip
方法的此方法的示例。在这个例子中,我的c#类根本不是通用的,但严格来说并不是必需的。
Add-Type @'
using System.Linq;
public class Zipper
{
public static object[] Zip(object[] first, object[] second)
{
return first.Zip(second, (f,s) => new { f , s}).ToArray();
}
}
'@
$a = 1..4;
[string[]]$b = "a","b","c","d";
[Zipper]::Zip($a, $b);
这会产生:
f s
- -
1 a
2 b
3 c
4 d
我确信有更好的PowerShell方法来“拉”两个阵列,但你明白了。我在这里提出的真正挑战是对Zip
进行硬编码(在C#类中)第3个参数,所以我不必弄清楚如何传入Func<T1, T2, TResult>
(也许有PowerShell方法也可以这样做吗?)。
答案 4 :(得分:1)
快速方式,如果没有名称冲突:
[Sample]::"MyMethod"("arg")