【问题标题】:Calling generic static method in PowerShell在 PowerShell 中调用通用静态方法
【发布时间】:2011-05-13 14:55:31
【问题描述】:

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

【问题讨论】:

    标签: c# powershell generics powershell-2.0


    【解决方案1】:

    这是 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# 包装器方法。

    【讨论】:

    • 你是对的方法..我为这个问题简化了它,可能有点过分了:-)
    【解决方案2】:

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

    【讨论】:

    • 对不起,但我坚持我的声明 - can't be done *directly* in PowerShell。 :-) BTW 很棒的解决方法……但实际上,PowerShell 团队需要修复这个漏洞。
    • 同意 Keith 的观点,如果有对此的内置支持会很好,但由于这是一个解决方案(即使它不是直接的),所以这个答案很受欢迎。
    • 解决OP不需要冗长的代码示例,MakeGenericMethod就足够了。
    【解决方案3】:

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

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

    【讨论】:

      【解决方案4】:

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

      【讨论】:

        【解决方案5】:

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

        同样,我有时也无法将泛型参数传递给方法……例如传递Func&lt;T1, T2, TResult&gt; 参数。

        一种对我来说比 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&lt;T1, T2, TResult&gt;(也许有一种 PowerShell 方法来也这样做?)。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2013-03-20
          相关资源
          最近更新 更多