简体   繁体   English

在PowerShell中调用通用静态方法

[英]Calling generic static method in PowerShell

How do you call a generic static method of a custom class in Powershell? 您如何在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: 并将其编译为程序集“ Classes.dll”,并按如下方式加载到PowerShell中:

Add-Type -Path "Classes.dll"

What's the easiest way to call the MyMethod method? 调用MyMethod方法最简单的方法是什么?

The easiest way to call MyMethod is, as @Athari says, to use MakeGenericMethod. 就像@Athari所说的那样,调用MyMethod的最简单方法是使用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

You can call generic methods, refer to the post Invoking Generic Methods on Non-Generic Classes in PowerShell . 您可以调用通用方法,请参阅《 在PowerShell中对非通用类调用通用方法》

This is not straightforward, you need to use MakeGenericMethod function. 这不是很简单,您需要使用MakeGenericMethod函数。 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"

This is a limitation of PowerShell and can't be done directly in PowerShell V1 or V2 AFAIK. 这是PowerShell的局限性,不能直接在PowerShell V1或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. 如果您拥有此代码并想在PowerShell中使用它,请避免使用通用方法或编写非通用C#包装器方法。

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. 好消息是,PowerShell v3更好地绑定到通用方法(并对其进行修饰?),您通常不必执行任何特殊操作,而是像调用普通方法一样进行调用。 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). 我无法指定现在可以使用的所有标准,但是根据我的经验,即使在PowerShell v4中,具有通用参数的某些情况仍然需要变通办法(可能是存在或过载或类似的东西)。

Similarly I also sometimes have trouble passing a generic parameter to a method ... for instance passing a Func<T1, T2, TResult> parameter. 同样,有时我也难以将通用参数传递给方法...例如,传递Func<T1, T2, TResult>参数。

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 ... 对我来说,一种解决方法比MakeGenericMethod或其他方法简单得多,它就是直接在我的脚本中直接放置一个快速的C#包装器类,然后让C#整理所有通用映射...

Here is an example of this approach that wraps the Enumerable.Zip method. 这是包装Enumerable.Zip方法的此方法的示例。 In this example my c# class isn't generic at all but that isn't strictly speaking necessary. 在此示例中,我的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);

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. 我敢肯定,有更好的PowerShell方法可以“压缩”两个数组,但是您可以理解。 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?). 我在这里躲避的真正挑战是对Zip进行硬编码(在C#类中)第3个参数,因此我不必弄清楚如何传递Func<T1, T2, TResult> (也许有一个PowerShell方式也可以做到这一点?)。

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

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM