Appel à la méthode statique générique dans PowerShell

Comment appelez-vous un générique méthode statique d'une classe personnalisée dans Powershell?

compte tenu de la classe suivante:

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

et cela est compilé en Classes d'assemblage.dll' et chargé dans PowerShell comme ceci:

Add-Type -Path "Classes.dll"

Quelle est la façon la plus facile d'appeler la méthode MyMethod?

20
demandé sur Athari 2010-11-22 06:19:40

5 réponses

vous pouvez appeler des méthodes génériques, référez-vous au post Invocation de Méthodes Génériques sur la Non-Classes Génériques en PowerShell.

Ce n'est pas simple, vous devez utiliser MakeGenericMethod fonction. C'est assez simple si la méthode n'a pas de dérogations, cela devient plus difficile si c'est le cas.

Juste au cas où, copie-collé de code à partir de là:

## 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"
12
répondu Athari 2013-11-19 20:40:34
$obj = New-Object Sample

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

sortie

Generic type is System.String with argument Test Message
Generic type is System.Double with argument Test Message
13
répondu JohnC 2014-03-30 02:22:23

ceci est une limitation de PowerShell et ne peut pas être fait directement dans PowerShell V1 ou V2 AFAIK.

BTW votre méthode générique n'est pas vraiment générique. Ne devrait-elle pas être:

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

si vous possédez ce code et que vous voulez l'utiliser à partir de PowerShell, évitez les méthodes génériques ou écrivez une méthode C# wrapper non-générique.

5
répondu Keith Hill 2010-11-22 06:06:22

la bonne nouvelle, C'est que PowerShell v3 est bien meilleur pour lier les méthodes génériques (et les reifier?) et souvent, vous n'avez pas à faire quelque chose de spécial, mais appelez-le comme une méthode normale. Je ne peux pas spécifier tous les critères pour lesquels cela fonctionne maintenant, mais selon mon expérience, certaines situations avec des paramètres génériques nécessitent encore des solutions de contournement, même dans PowerShell v4 (peut-être son existence ou des surcharges ou quelque chose de similaire).

de même, j'ai parfois du mal à passer une paramètre générique d'une méthode ... par exemple le passage d'un Func<T1, T2, TResult> paramètre.

pour moi, il y a une solution beaucoup plus simple que MakeGenericMethod ou d'autres approches, c'est de mettre une Classe C# wrapper rapide directement dans mon script, et laisser C# trier tous les mappages génériques ...

Voici un exemple de cette approche qui englobe l' Enumerable.Zip méthode. Dans cet exemple, ma Classe c# n'est pas générique du tout, mais ce n'est pas strictement nécessaire.

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

Ce produit:

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

je suis sûr qu'il y a de meilleurs moyens PowerShell pour "Zip" deux tableaux, mais vous avez l'idée. Le vrai défi que j'ai esquivé ici était d'avoir un 3e paramètre codé dur (dans la Classe C#) à Zip donc je n'avais pas à comprendre comment passer dans cette

2
répondu TCC 2015-01-29 00:54:46

Fast way, s'il n'y a pas de conflits de noms:

[Sample]::"MyMethod"("arg")
1
répondu Zenexer 2013-07-15 04:31:47