18

What is the syntax to pass .NET method as a delegate callback to a .NET object in PowerShell.

For example:

C#:

public class Class1
{
    public static void MyMethod(Action<object> obj)
    {
         obj("Hey!");
    }
}

public class Class2
{
    public static void Callback(object obj)
    {
         Console.Writeline(obj.ToString());
    }
}

PowerShell:

[Class1]::MyMethod([Class2]::Callback)

This doesn't work.

abatishchev
  • 98,240
  • 88
  • 296
  • 433
Adam Driscoll
  • 9,395
  • 9
  • 61
  • 104
  • Just for completeness, a couple of links on Oisin Grehan's site that helped clear things up for me: http://www.nivot.org/post/2009/07/18/PowerShell20RCWorkingWithNETCallbacksPart1Synchronous.aspx http://www.nivot.org/post/2009/10/09/PowerShell20AsynchronousCallbacksFromNET.aspx – Tao Sep 02 '12 at 00:01

5 Answers5

13

Working code via Adam's and Oisin's chat.

Add-Type -Language CSharpVersion3 -TypeDefinition @"
using System;

public class Class1
{
    public static void MyMethod(Action<object> obj)
    {
         obj("Hey!");
    }
}

public class Class2
{
    public static void Callback(object obj)
    {
         Console.WriteLine(obj.ToString());
    }
}
"@

$method   = [Class2].GetMethod("Callback") 
$delegate = [System.Delegate]::CreateDelegate([System.Action[Object]], $null, $method)

[Class1]::MyMethod($delegate)
Doug Finke
  • 6,675
  • 1
  • 31
  • 47
4

Via @oising on Twitter:

@adamdriscoll you're spoiled by the c# compiler's delegate inferencing. You need to new up that action explicitly, old school styley.

$method = [Class2].GetMethod("Callback") 
$delegate = [System.Delegate]::CreateDelegate([System.Action[Object]], $null, $method
[Class1]::MyMethod($delegate)
John
  • 15,418
  • 12
  • 44
  • 65
Adam Driscoll
  • 9,395
  • 9
  • 61
  • 104
4
$code = @'
using System;
public class Class1
{    
    public static void MyMethod(Action<object> obj)    
    {
        obj("Hey!");    
    }
}
public class Class2
{    
    public static void Callback(object obj)    
    {         
        Console.WriteLine(obj.ToString());    
    }
}
'@

Add-Type -TypeDefinition $code -Language CSharpVersion3

[class1]::mymethod([system.action]::CreateDelegate('System.Action[Object]', [class2].getmethod('Callback')))
Steven Murawski
  • 10,959
  • 41
  • 53
3

The type of [Class2]::Callback is System.Management.Automation.PSMethod which apparently cannot be converted by the core to the required delegate.

I am not sure that this is the best way of solving the task (I have not seen any kind of official documentation about this) but the code below works for me in this example case and other similar cases in my practice:

[Class1]::MyMethod({ [Class2]::Callback($args[0]) })

The core is able to convert our script block { [Class2]::Callback($args[0]) } to the required delegate.

P.S. Though not directly related to the question but here is yet another working example of this technique: using script blocks as match evaluator delegates in regular expressions: How to sort by file name the same way Windows Explorer does?

Community
  • 1
  • 1
Roman Kuzmin
  • 40,627
  • 11
  • 95
  • 117
  • Thanks for the response. The issue here is I was trying to avoid wrapping the call in a script block because of some wacky threading stuff I was doing. This does in fact work but will not work in my circumstance. I was looking for a way to do it directly. – Adam Driscoll Apr 03 '11 at 17:11
  • I am really interested in the reasons why it does not work with your "wacky threading stuff". What is the error/problem? Is it something like "There is no runspace ...", perhaps? – Roman Kuzmin Apr 03 '11 at 17:20
  • Yep. I need to invoke something in a new thread but cannot invoke a script block because there is no default runspace in that thread. I'm working on a blog post about it so there will be more details about it later this evening. – Adam Driscoll Apr 03 '11 at 17:25
  • Yes, this is the problem, indeed. In some cases if your C# code that starts that thread is PowerShell aware then the first thing you can do in that thread is `Runspace.DefaultRunspace = ;`. This trick works fine in my custom host with some threading stuff. – Roman Kuzmin Apr 03 '11 at 17:33
1

I am not an expert at C#, but after reading a couple articles it seems that you are trying to use generic delegates. Your Callback method is not a generic delegate, it isn't even a delegate.

I think this is what you need to do:

C#

public class Class1
{
    public static void MyMethod(Action<object> obj)
    {
         obj("Hey!");
    }
}

public class Class2
{
    public Action<object> CallBack = obj => Console.WriteLine(obj.ToString());
}

Powershell:

[Class1]::MyMethod([Class2]::Callback)
JasonMArcher
  • 14,195
  • 22
  • 56
  • 52
  • 2
    Thanks for response but in C# the call back method does match the signature of the delegate. Your definition is an example of a lambda function that, if you were to decompile with something like Reflector would also pretty closely match the method definition of "Callback" – Adam Driscoll Apr 03 '11 at 17:18
  • How would one create a delegate for a PSMethod/PSScriptMethod? `$o = new-object psobject; $o | Add-Member -MemberType ScriptMethod -Name "test" -Value { return "some object" }` `$o.test` (without parenthesis will tell me it's a PSScriptMethod, and that has a based of PSMethod) – wesm Nov 23 '14 at 23:32