代码之家  ›  专栏  ›  技术社区  ›  David Gardiner

在PowerShell中调用泛型静态方法

  •  21
  • David Gardiner  · 技术社区  · 14 年前

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

    5 回复  |  直到 10 年前
        1
  •  13
  •   Athari    11 年前

    可以调用泛型方法,请参阅文章 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
  •  14
  •   JohnC agilejoshua    11 年前

    正如@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
    
        3
  •  5
  •   Keith Hill    14 年前

    这是PowerShell的一个限制,不能直接在PowerShell V1或V2 AFAIK中完成。

    顺便说一句,你的泛型方法并不是真正的泛型方法。不应该是:

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

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

        4
  •  2
  •   TCC    10 年前

    好消息是,PowerShell v3在绑定到泛型方法(并具体化它们)方面做得更好你通常不需要做任何特别的事情,只要像平常一样调用它。我不能指定所有现在运行的标准,但是在我的经验中,某些通用参数的情况甚至需要在PultSeaveV4(可能是存在或重载或类似的情况下)。

    类似地,我有时也很难将泛型参数传递给方法。。。例如通过 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方法来“压缩”两个数组,但你明白了。我在这里回避的真正挑战是有一个硬编码(在C类中)的第三个参数 Zip 所以我不必想办法 函数<T1,T2,结果> (也许还有一个超级地狱的方法可以做到这一点?).

        5
  •  1
  •   Zenexer    11 年前

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

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