Вызов универсального статического метода в PowerShell - PullRequest
21 голосов
/ 22 ноября 2010

Как назвать универсальный статический метод пользовательского класса в 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 ]

14 голосов
/ 29 марта 2014

Самый простой способ вызвать MyMethod, как говорит @Athari, использовать 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
12 голосов
/ 22 ноября 2010

Вы можете вызывать универсальные методы, см. Пост Вызов универсальных методов для неуниверсальных классов в 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"
5 голосов
/ 22 ноября 2010

Это ограничение 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 голосов
/ 29 января 2015

Хорошая новость заключается в том, что PowerShell v3 намного лучше связывается с универсальными методами (и изменяет их?), И вам часто не нужно делать ничего особенного, кроме как вызывать его, как обычный метод. Я не могу указать все критерии, для которых это теперь работает, но по моему опыту некоторые ситуации с общими параметрами все еще требуют обходных путей даже в PowerShell v4 (возможно, это наличие или перегрузки или что-то подобное).

Аналогичным образом у меня иногда возникают проблемы при передаче универсального параметра в метод ... например, при передаче параметра 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 #) 3-й параметр в Zip, поэтому мне не пришлось выяснять, как передать этот Func<T1, T2, TResult> (возможно, есть способ PowerShell для и что делать?).

1 голос
/ 15 июля 2013

Быстрый способ, если нет конфликтов имен:

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