PoshCode Logo PowerShell Code Repository

Get-Delegate by Oisin Grehan 4 years ago
embed code: <script type="text/javascript" src="http://PoshCode.org/embed/3327"></script>download | new post

Requires PowerShell 3.0. This is intended as the definitive Get-Delegate function: Create delegates for any methods, static or instance, with any signature, using an intuitive syntax. Create using explicit delegate types, or by specifying a parameter type array to select the correct method overload. Full help with examples inluded: get-help get-delegate -full | more; use -verbose for additional detail of the conversion process. Includes test functions below.

  1. #requires -version 3
  2.  
  3. function Get-Delegate {
  4. <#
  5. .SYNOPSIS
  6. Create an action[] or func[] delegate for a psmethod reference.
  7. .DESCRIPTION
  8. Create an action[] or func[] delegate for a psmethod reference.
  9. .PARAMETER Method
  10. A PSMethod reference to create a delegate for. This parameter accepts pipeline input.
  11. .PARAMETER ParameterType
  12. An array of types to use for method overload resolution. If there are no overloaded methods
  13. then this array will be ignored but a warning will be omitted if the desired parameters were
  14. not compatible.
  15. .PARAMETER DelegateType
  16. The delegate to create for the corresponding method. Example: [string]::format | get-delegate -delegatetype func[int,string]
  17. .INPUTS System.Management.Automation.PSMethod, System.Type[]
  18. .EXAMPLE
  19. $delegate = [string]::format | Get-Delegate string,string
  20.  
  21. Gets a delegate for a matching overload with string,string parameters.
  22. It will actually return func<object,string> which is the correct
  23. signature for invoking string.format with string,string.
  24. .EXAMPLE
  25. $delegate = [console]::beep | Get-Delegate @()
  26.  
  27. Gets a delegate for a matching overload with no parameters.
  28. .EXAMPLE
  29. $delegate = [console]::beep | get-delegate int,int
  30.  
  31. Gets a delegate for a matching overload with @(int,int) parameters.
  32. .EXAMPLE
  33. $delegate = [string]::format | Get-Delegate -Delegate 'func[string,object,string]'
  34.  
  35. Gets a delegate for an explicit func[].
  36. .EXAMPLE
  37. $delegate = [console]::writeline | Get-Delegate -Delegate 'action[int]'
  38.  
  39. Gets a delegate for an explicit action[].
  40. .EXAMPLE
  41. $delegate = [string]::isnullorempty | get-delegate
  42.  
  43. For a method with no overloads, we will choose the default method and create a corresponding action/action[] or func[].
  44. #>
  45.     [CmdletBinding(DefaultParameterSetName="FromParameterType")]
  46.     [outputtype('System.Action','System.Action[]','System.Func[]')]
  47.     param(
  48.         [parameter(mandatory=$true, valuefrompipeline=$true)]
  49.         [system.management.automation.psmethod]$Method,
  50.  
  51.         [parameter(position=0, valuefromremainingarguments=$true, parametersetname="FromParameterType")]
  52.         [validatenotnull()]
  53.         [allowemptycollection()]
  54.         [Alias("types")]
  55.         [type[]]$ParameterType = @(),
  56.  
  57.         [parameter(mandatory=$true, parametersetname="FromDelegate")]
  58.         [validatenotnull()]
  59.         [validatescript({ ([delegate].isassignablefrom($_)) })]
  60.         [type]$DelegateType
  61.     )
  62.  
  63.     $base = $method.GetType().GetField("baseObject","nonpublic,instance").GetValue($method)    
  64.    
  65.     if ($base -is [type]) {
  66.         [type]$baseType = $base
  67.         [reflection.bindingflags]$flags = "Public,Static"
  68.     } else {
  69.         [type]$baseType = $base.GetType()
  70.         [reflection.bindingflags]$flags = "Public,Instance"
  71.     }
  72.  
  73.     if ($pscmdlet.ParameterSetName -eq "FromDelegate") {
  74.         write-verbose "Inferring from delegate."
  75.  
  76.         if ($DelegateType -eq [action]) {
  77.             # void action        
  78.             $ParameterType = [type[]]@()
  79.        
  80.         } elseif ($DelegateType.IsGenericType) {
  81.             # get type name
  82.             $name = $DelegateType.Name
  83.  
  84.             # is it [action[]] ?
  85.             if ($name.StartsWith("Action``")) {
  86.    
  87.                 $ParameterType = @($DelegateType.GetGenericArguments())    
  88.            
  89.             } elseif ($name.StartsWith("Func``")) {
  90.    
  91.                 # it's a [func[]]
  92.                 $ParameterType = @($DelegateType.GetGenericArguments())
  93.                 $ParameterType = $ParameterType[0..$($ParameterType.length - 2)] # trim last element (TReturn)
  94.             } else {
  95.                 throw "Unsupported delegate type: Use Action<> or Func<>."
  96.             }
  97.         }
  98.     }
  99.  
  100.     [reflection.methodinfo]$methodInfo = $null
  101.  
  102.     if ($Method.OverloadDefinitions.Count -gt 1) {
  103.         # find best match overload
  104.         write-verbose "$($method.name) has multiple overloads; finding best match."
  105.  
  106.         $finder = [type].getmethod("GetMethodImpl", [reflection.bindingflags]"NonPublic,Instance")
  107.  
  108.         write-verbose "base is $($base.gettype())"
  109.  
  110.         $methodInfo = $finder.invoke(
  111.             $baseType,
  112.              @(
  113.                   $method.Name,
  114.                   $flags,
  115.                   $null,
  116.                   $null,
  117.                   [type[]]$ParameterType,
  118.                   $null
  119.              )
  120.         ) # end invoke
  121.     } else {
  122.         # method not overloaded
  123.         Write-Verbose "$($method.name) is not overloaded."
  124.         if ($base -is [type]) {
  125.             $methodInfo = $base.getmethod($method.name, $flags)
  126.         } else {
  127.             $methodInfo = $base.gettype().GetMethod($method.name, $flags)
  128.         }
  129.  
  130.         # if parametertype is $null, fill it out; if it's not $null,
  131.         # override it to correct it if needed, and warn user.
  132.         if ($pscmdlet.ParameterSetName -eq "FromParameterType") {          
  133.             if ($ParameterType -and ((compare-object $parametertype $methodinfo.GetParameters().parametertype))) {
  134.                 write-warning "Method not overloaded: Ignoring provided parameter type(s)."
  135.             }
  136.             $ParameterType = $methodInfo.GetParameters().parametertype
  137.             write-verbose ("Set default parameters to: {0}" -f ($ParameterType -join ","))
  138.         }
  139.     }
  140.  
  141.     if (-not $methodInfo) {
  142.         write-warning "Could not find matching signature for $($method.Name) with $($parametertype.count) parameter(s)."
  143.     } else {
  144.        
  145.         write-verbose "MethodInfo: $methodInfo"
  146.  
  147.         # it's important here to use the actual MethodInfo's parameter types,
  148.         # not the desired types ($parametertype) because they may not match,
  149.         # e.g. asked for method(int) but match is method(object).
  150.  
  151.         if ($pscmdlet.ParameterSetName -eq "FromParameterType") {            
  152.            
  153.             if ($methodInfo.GetParameters().count -gt 0) {
  154.                 $ParameterType = $methodInfo.GetParameters().ParameterType
  155.             }
  156.            
  157.             # need to create corresponding [action[]] or [func[]]
  158.             if ($methodInfo.ReturnType -eq [void]) {
  159.                 if ($ParameterType.Length -eq 0) {
  160.                     $DelegateType = [action]
  161.                 } else {
  162.                     # action<...>
  163.                    
  164.                     # replace desired with matching overload parameter types
  165.                     #$ParameterType = $methodInfo.GetParameters().ParameterType
  166.                     $DelegateType = ("action[{0}]" -f ($ParameterType -join ",")) -as [type]
  167.                 }
  168.             } else {
  169.                 # func<...>
  170.  
  171.                 # replace desired with matching overload parameter types
  172.                 #$ParameterType = $methodInfo.GetParameters().ParameterType
  173.                 $DelegateType = ("func[{0}]" -f (($ParameterType + $methodInfo.ReturnType) -join ",")) -as [type]
  174.             }                        
  175.         }
  176.         Write-Verbose $DelegateType
  177.  
  178.         if ($flags -band [reflection.bindingflags]::Instance) {
  179.             $methodInfo.createdelegate($DelegateType, $base)
  180.         } else {
  181.             $methodInfo.createdelegate($DelegateType)
  182.         }
  183.     }
  184. }
  185.  
  186. #
  187. # tests
  188. #
  189.  
  190. function Assert-True {
  191.     param(
  192.         [parameter(position=0, mandatory=$true)]
  193.         [validatenotnull()]
  194.         [scriptblock]$Script,
  195.  
  196.         [parameter(position=1)]
  197.         [validatenotnullorempty()]
  198.         [string]$Name = "Assert-True"
  199.     )    
  200.     $eap = $ErrorActionPreference
  201.     Write-Host -NoNewline "Assert-True [ $Name ] "
  202.     try {
  203.         $erroractionpreference = "stop"
  204.         if ((& $script) -eq $true) {
  205.             write-host -ForegroundColor Green "[PASS]"
  206.             return
  207.         }
  208.         $reason = "Assert failed."
  209.     }
  210.     catch {
  211.         $reason = "Error: $_"
  212.     }
  213.     finally {
  214.         $ErrorActionPreference = $eap
  215.     }
  216.     write-host -ForegroundColor Red "[FAIL] " -NoNewline
  217.     write-host "Reason: '$reason'"
  218. }
  219.  
  220. #
  221. # static methods
  222. #
  223.  
  224. assert-true {
  225.     $delegate = [string]::format | Get-Delegate -Delegate 'func[string,object,string]'
  226.     $delegate.invoke("hello, {0}", "world") -eq "hello, world"
  227. } -name "[string]::format | get-delegate -delegate 'func[string,object,string]'"
  228.  
  229. assert-true {
  230.     $delegate = [console]::writeline | Get-Delegate -Delegate 'action[int]'
  231.     $delegate -is [action[int]]
  232. } -name "[console]::writeline | get-delegate -delegate 'action[int]'"
  233.  
  234. assert-true {
  235.     $delegate = [string]::format | Get-Delegate string,string
  236.     $delegate.invoke("hello, {0}", "world") -eq "hello, world"
  237. } -name "[string]::format | get-delegate string,string"
  238.  
  239. assert-true {
  240.     $delegate = [console]::beep | Get-Delegate @()
  241.     $delegate -is [action]
  242. } -name "[console]::beep | get-delegate @()"
  243.  
  244. assert-true {
  245.     $delegate = [console]::beep | Get-Delegate -DelegateType action
  246.     $delegate -is [action]
  247. } -name "[console]::beep | Get-Delegate -DelegateType action"
  248.  
  249. assert-true {
  250.     $delegate = [string]::IsNullOrEmpty | get-delegate
  251.     $delegate -is [func[string,bool]]
  252. } -name "[string]::IsNullOrEmpty | get-delegate # single overload"
  253.  
  254. assert-true {
  255.     $delegate = [string]::IsNullOrEmpty | get-delegate string
  256.     $delegate -is [func[string,bool]]
  257. } -name "[string]::IsNullOrEmpty | get-delegate string # single overload"
  258.  
  259. #
  260. # instance methods
  261. #
  262.  
  263. assert-true {
  264.     $sb = new-object text.stringbuilder
  265.     $delegate = $sb.Append | get-delegate string
  266.     $delegate -is [System.Func[string,System.Text.StringBuilder]]
  267. } -name "`$sb.Append | get-delegate string"
  268.  
  269. assert-true {
  270.     $sb = new-object text.stringbuilder
  271.     $delegate = $sb.AppendFormat | get-delegate string, int, int
  272.     $delegate -is [System.Func[string,object,object,System.Text.StringBuilder]]
  273. } -name "`$sb.AppendFormat | get-delegate string, int, int"

Submit a correction or amendment below (
click here to make a fresh posting)
After submitting an amendment, you'll be able to view the differences between the old and new posts easily.

Syntax highlighting:


Remember me