15

For example I have a .NET object $m with the following method overloads:

PS C:\Users\Me> $m.GetBody

OverloadDefinitions
-------------------    
T GetBody[T]() 
T GetBody[T](System.Runtime.Serialization.XmlObjectSerializer serializer)  

If I try to invoke the parameterless method I get:

PS C:\Users\Me> $m.GetBody()
Cannot find an overload for "GetBody" and the argument count: "0".
At line:1 char:1
+ $m.GetBody()
+ ~~~~~~~~~~~~
    + CategoryInfo          : NotSpecified: (:) [], MethodException
    + FullyQualifiedErrorId : MethodCountCouldNotFindBest

I understand PowerShell v3.0 is supposed to work more easily with generics. Obviously I need to tell it somehow what type I want returned but I cannot figure out the syntax.

marc_s
  • 732,580
  • 175
  • 1,330
  • 1,459
Jack Ukleja
  • 13,061
  • 11
  • 72
  • 113

4 Answers4

17

It looks like you are trying to invoke a generic method.

In powershell this can be done by:

$nonGenericClass = New-Object NonGenericClass
$method = [NonGenericClass].GetMethod("SimpleGenericMethod")
$gMethod = $method.MakeGenericMethod([string]) 
# replace [string] with the type you want to use for T. 
$gMethod.Invoke($nonGenericClass, "Welcome!")

See this wonderful blog post for more info and additional examples.

For your example you could try:

$Source = @" 
public class TestClass
{
    public T Test<T>()
    {
        return default(T);
    }
    public int X;
}
"@ 

Add-Type -TypeDefinition $Source -Language CSharp 
$obj = New-Object TestClass

$Type  = $obj.GetType();
$m =  $Type.GetMethod("Test")
$g = new-object system.Guid
$gType = $g.GetType()
$gm = $m.MakeGenericMethod($gType)
$out = $gm.Invoke( $obj, $null)
#$out will be the default GUID (all zeros)

This can be simplified by doing:

$Type.GetMethod("Test").MakeGenericMethod($gType).Invoke( $obj, $null)

This has been testing in powershell 2 and powershell 3.

If you had a more detailed example of how you came across this generic method I would be able to give more details. I have yet to see any microsoft cmdlets return anything that give you generic methods. The only time this comes up is when custom objects or methods from c# or vb.net are used.

To use this without any parameters you can use Invoke with just the first parameter. $gMethod.Invoke($nonGenericClass)

Chad Carisch
  • 2,422
  • 3
  • 22
  • 30
  • 1
    Yes, its a generic method as stated in the question title :) I'll update to clarify this is .NET object created with new-object. Lees post also claims in PSv3 you don't need any of that ugly stuff...the problem is the method I want to call has no input parameters hence T cannot be inferred. – Jack Ukleja Sep 12 '13 at 23:09
  • 1
    I'm pretty sure you still have to resort to using reflection in this scenario. V3 improved consumption of generics but there still are some rough edges. – Keith Hill Sep 13 '13 at 04:43
  • V3 only improves on creating generic objects such as a Generic List. I feel like if you really need generics, you should probably look into writing a console application that utilises powershell functions, however handles generics in C# or VB.net. – Chad Carisch Sep 13 '13 at 22:20
  • @Schneider , I am interested to know if this works for you. I did some more research to find that psv3 does not improve generic methods, just creating generic objects. – Chad Carisch Sep 15 '13 at 17:36
  • 1
    This answer and code sample does not answer OP which has a method with overloads. However the referenced article does explain how to handle overloads. – JohnC Mar 29 '14 at 18:05
6

Calling a generic method on an object instance:

$instance.GetType().GetMethod('MethodName').MakeGenericMethod([TargetType]).Invoke($instance, $parameters)

Calling a static generic method (see also Calling generic static method in PowerShell):

[ClassType].GetMethod('MethodName').MakeGenericMethod([TargetType]).Invoke($null, $parameters)

Note that you will encounter an AmbiguousMatchException when there is also a non-generic version of the method (see How do I distinguish between generic and non generic signatures using GetMethod in .NET?). Use GetMethods() then:

([ClassType].GetMethods() | where {$_.Name -eq "MethodName" -and $_.IsGenericMethod})[0].MakeGenericMethod([TargetType]).Invoke($null, $parameters)

(Mind that there could be more than one method that match the above filter, so make sure to adjust it to find the one you need.)

Hint: You can write complex generic type literals like this (see Generic type of Generic Type in Powershell):

[System.Collections.Generic.Dictionary[int,string[]]]
Community
  • 1
  • 1
marsze
  • 15,079
  • 5
  • 45
  • 61
3

To call a (parameterless) generic method with overloads from Powershell v3, as shown in the OP example, use the script Invoke-GenericMethod.ps1 from the reference provided by @Chad Carisch, Invoking Generic Methods on Non-Generic Classes in PowerShell.

It should look something like

Invoke-GenericMethod $m GetBody T @()

This is a verified working code sample that I am using:

[System.Reflection.Assembly]::LoadWithPartialName("Microsoft.Practices.ServiceLocation") | Out-Null
[System.Reflection.Assembly]::LoadWithPartialName("Microsoft.Practices.SharePoint.Common") | Out-Null

$serviceLocator = [Microsoft.Practices.SharePoint.Common.ServiceLocation.SharePointServiceLocator]::GetCurrent()

# Want the PowerShell equivalent of the following C#
# config = serviceLocator.GetInstance<IConfigManager>();

# Cannot find an overload for "GetInstance" and the argument count: "0".
#$config = $serviceLocator.GetInstance()

# Exception calling "GetMethod" with "1" argument(s): "Ambiguous match found."
#$config = $serviceLocator.GetType().GetMethod("GetInstance").MakeGenericMethod([IConfigManager]).Invoke($serviceLocator)

# Correct - using Invoke-GenericMethod
$config = C:\Projects\SPG2013\Main\Scripts\Invoke-GenericMethod $serviceLocator GetInstance Microsoft.Practices.SharePoint.Common.Configuration.IConfigManager @()

$config.CanAccessFarmConfig

Here is an alternate script that I haven't tried but is more recent and being actively maintained, Invoke Generic Methods from PowerShell.

mklement0
  • 382,024
  • 64
  • 607
  • 775
JohnC
  • 1,797
  • 1
  • 18
  • 26
3

Update: PowerShell (Core) 7.3+ now does support calling generic methods with explicit type arguments.

 # PS v7.3+ only; using [string] as an example type argument.
 $m.GetBody[string]()

PowerShell (Core) 7.2- and Windows PowerShell:

marsze's helpful answer contains great general information about calling generic methods, but let me address the aspect of calling a parameter-less one specifically, as asked:

As hinted at in the question:

  • in PSv3+ PowerShell can infer the type from the parameter values (arguments) passed to a generic method,
  • which by definition cannot work with a parameter-less generic method, because there is nothing to infer the type from.

Prior to PowerShell (Core) 7.3, PowerShell previously had no syntax that would allow you to specify the type explicitly in this scenario.

In such older versions, reflection must be used:

# Invoke $m.GetBody[T]() with [T] instantiated with type [decimal]
$m.GetType().GetMethod('GetBody', [type[]] @()).
    MakeGenericMethod([decimal]).
      Invoke($m, @())
  • .GetMethod('GetBody', [type[]] @()) unambiguously finds the parameter-less overload of.GetBody(), due to passing in an empty array of parameter types.

  • .MakeGenericMethod([decimal]) instantiates the method with example type [decimal].

  • .Invoke($m, @()) then invokes the type-instantiated method on input object ($m) with no arguments (@(), the empty array).

mklement0
  • 382,024
  • 64
  • 607
  • 775