在 PowerShell 中调用通用静态方法

发布于 2024-10-03 07:40:37 字数 415 浏览 3 评论 0原文

如何在 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 方法的最简单方法是什么?

How do you call a generic static method of a custom class in Powershell?

Given the following class:

public class Sample
{
    public static string MyMethod<T>( string anArgument )
    {
        return string.Format( "Generic type is {0} with argument {1}", typeof(T), anArgument );
    }
}

And this is compiled into an assembly 'Classes.dll' and loaded into PowerShell like this:

Add-Type -Path "Classes.dll"

What's the easiest way to call the MyMethod method?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

要走就滚别墨迹 2024-10-10 07:40:37

正如 @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

The easiest way to call MyMethod is, as @Athari says, to use MakeGenericMethod. Since he doesn't actually show how to do that, here is a verified working code sample:

$obj = New-Object Sample

$obj.GetType().GetMethod("MyMethod").MakeGenericMethod([String]).Invoke($obj, "Test Message")
$obj.GetType().GetMethod("MyMethod").MakeGenericMethod([Double]).Invoke($obj, "Test Message")

with output

Generic type is System.String with argument Test Message
Generic type is System.Double with argument Test Message
魔法唧唧 2024-10-10 07:40:37

可以调用泛型方法,参考帖子 在 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"

You can call generic methods, refer to the post Invoking Generic Methods on Non-Generic Classes in PowerShell.

This is not straightforward, you need to use MakeGenericMethod function. It is pretty simple if method doesn't have overrides, it gets harder if it does.

Just in case, copy-pasted code from there:

## 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"
メ斷腸人バ 2024-10-10 07:40:37

这是 PowerShell 的限制,无法直接在 PowerShell V1 或 V2 中完成(据我所知)。

顺便说一句,您的通用方法并不是真正通用的。不应该是这样吗:

public static string MyMethod<T>(T anArgument)
{ 
   return string.Format( "Generic type is {0} with argument {1}", 
                         typeof(T), anArgument.ToString()); 
} 

如果您拥有此代码并希望从 PowerShell 使用它,请避免泛型方法或编写非泛型 C# 包装器方法。

This is a limitation of PowerShell and can't be done directly in PowerShell V1 or V2 AFAIK.

BTW your generic method isn't really generic. Shouldn't it be:

public static string MyMethod<T>(T anArgument)
{ 
   return string.Format( "Generic type is {0} with argument {1}", 
                         typeof(T), anArgument.ToString()); 
} 

If you own this code and want to use it from PowerShell, avoid generic methods or write a non-generic C# wrapper method.

∞觅青森が 2024-10-10 07:40:37

好消息是 PowerShell v3 在绑定到通用方法(并具体化它们?)方面要好得多,并且您通常不需要做任何特殊的事情,只需像调用普通方法一样调用它即可。我无法指定现在适用的所有标准,但根据我的经验,即使在 PowerShell v4 中,通用参数的某些情况仍然需要解决方法(可能是存在或重载或类似的东西)。

同样,我有时也无法将通用参数传递给方法......例如传递 Func 参数。

对我来说,一种比 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# 类中)的第三个参数,因此我不必弄清楚如何传入该 Func (也许还有一种 PowerShell 方法可以做到这一点?)。

The good news is PowerShell v3 is much better at binding to generic methods (and reifying them?) and you often don't have to do anything special but call it as you would a normal method. I can't specify all of the criteria for which this now works, but in my experience certain situations with generic parameters still require workarounds even in PowerShell v4 (maybe its the existence or overloads or something similar).

Similarly I also sometimes have trouble passing a generic parameter to a method ... for instance passing a Func<T1, T2, TResult> parameter.

One work-around that to me is much simpler than MakeGenericMethod or other approaches is to just put a quick C# wrapper class directly in my script, and let C# sort out all the generic mapping ...

Here is an example of this approach that wraps the Enumerable.Zip method. In this example my c# class isn't generic at all but that isn't strictly speaking necessary.

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);

This produces:

 f s
 - -
 1 a
 2 b
 3 c
 4 d

I'm sure there are better PowerShell ways to "Zip" two arrays but you get the idea. The real challenge that I dodged here was having a hard-coded (in the C# class) 3rd parameter to Zip so I didn't have to figure out how to pass in that Func<T1, T2, TResult> (Maybe there is a PowerShell way to do that as well?).

小情绪 2024-10-10 07:40:37

如果没有名称冲突,快速方法:

[Sample]::"MyMethod"("arg")

Fast way, if there are no name conflicts:

[Sample]::"MyMethod"("arg")
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文