Creating arbitrary delegates from scriptblocks in PowerShell…

PowerShell Team

People have been asking about creating arbitrary delegates out of scriptblocks. As Jeffrey has mentioned, this isn’t directly supported in V1.0 of PowerShell. It is, however, possible to do it using dynamic methods and the CreateDelegate call. Here’s a script (also attached) that will do it. I haven’t tested this very much so it may not work in all cases but it should give you an idea of what’s possible. Note – this is a fairly extreme (pathological?) example of what can be done in PowerShell. You can use the script as shown in the following example:

 

$delegate = ./get-delegate `

System.Text.RegularExpressions.MatchEvaluator {

    # Return a replacement for the matching string…
    “<$($args[0].ToString().ToLower())>”

    # and count the number of replacements…
    $global:PatternCount++
}

 

$re = [regex] “[A-Z]”

 

# now transform some text…

 

get-help about_Assignment_operators |
   %{ $re.Replace($_, $delegate) }

 

# And display the number of replacements that were done…

“`nNumber of replacements: $PatternCount”

 

This example uses the MatchEvaluator delegate to do some transformations on a piece of text. The script text follows… (updated – I missed the type argument to the box instruction in the first version posted.)

 

Bruce Payette

PowerShell Tech Lead

 

(And for more hardcore hacking – check out Lee’s blog entry:

http://www.leeholmes.com/blog/MorePInvokeInPowerShell.aspx )

 

——————–cut here———————————————–
param([type] $type, [ScriptBlock] $scriptBlock)

# Helper function to emit an IL opcode
function emit($opcode)
{
    if ( ! ($op = [System.Reflection.Emit.OpCodes]::($opcode)))
    {
        throw “new-method: opcode ‘$opcode’ is undefined”
    }

    if ($args.Length -gt 0)
    {
        $ilg.Emit($op, $args[0])
    }
    else
    {
        $ilg.Emit($op)
    }
}

# Get the method info for this delegate invoke…
$delegateInvoke = $type.GetMethod(“Invoke”)

# Get the argument type signature for the delegate invoke
$parameters = @($delegateInvoke.GetParameters())
$returnType = $delegateInvoke.ReturnParameter.ParameterType

$argList = new-object Collections.ArrayList
[void] $argList.Add([ScriptBlock])
foreach ($p in $parameters)
{
    [void] $argList.Add($p.ParameterType);
}

$dynMethod = new-object reflection.emit.dynamicmethod (“”,
    $returnType, $argList.ToArray(), [object], $false)
$ilg = $dynMethod.GetILGenerator()

# Place the scriptblock on the stack for the method call
emit Ldarg_0

emit Ldc_I4 ($argList.Count – 1)  # Create the parameter array
emit Newarr ([object])

for ($opCount = 1; $opCount -lt $argList.Count; $opCount++)
{
    emit Dup                    # Dup the array reference
    emit Ldc_I4 ($opCount – 1); # Load the index
    emit Ldarg $opCount         # Load the argument
    if ($argList[$opCount].IsValueType) # Box if necessary
 {
        emit Box $argList[$opCount]
 }
    emit Stelem ([object])  # Store it in the array
}

# Now emit the call to the ScriptBlock invoke method
emit Call ([ScriptBlock].GetMethod(“InvokeReturnAsIs”))

if ($returnType -eq [void])
{
    # If the return type is void, pop the returned object
    emit Pop
}
else
{
    # Otherwise emit code to convert the result type which looks
    # like LanguagePrimitives.ConvertTo(value, type)

    $signature = [object], [type]
    $convertMethod =
        [Management.Automation.LanguagePrimitives].GetMethod(
            “ConvertTo”, $signature);
    $GetTypeFromHandle = [Type].GetMethod(“GetTypeFromHandle”);
    emit Ldtoken $returnType  # And the return type token…
    emit Call $GetTypeFromHandle
    emit Call $convertMethod
}
emit Ret

#
# Now return a delegate from this dynamic method…
#

$dynMethod.CreateDelegate($type, $scriptBlock)

get-delegate.ps1

0 comments

Discussion is closed.

Feedback usabilla icon