可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
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.
回答1:
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)
回答2:
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[]]]
回答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.
回答4:
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.
As of Windows PowerShell v5.1 / PowerShell Core v6.1.0, PowerShell has no syntax that would allow you to specify the type explicitly in this scenario.
However, there is a suggestion on GitHub to enhance the syntax in PowerShell Core that has been green-lighted in principle, but is awaiting implementation by the community.
For now, 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).