tags:

views:

189

answers:

3

I'm using IronRuby and trying to come up with a seamless to pass a block to C#. This question is a follow on from How to use an IronRuby block in C# which indicates blocks cannot be passed between IronRuby and C#.

My subsequent question is as to whether there's a way to acheive the same goal using a Ruby wrapper method to make the block into a lambda or Proc object and pass it to C#?

Some code that's along the lines of what I'd like to be able to do is below. The code doesn't work but hopefully the intent is clear enough.

string scriptText =
    @"def BlockTest
     result = csharp.BlockTest somehow-pass-the-block ("hello")
     puts result
    end
    BlockTest { |arg| arg + 'world'}";

Console.WriteLine("Compiling IronRuby script.");
ScriptEngine scriptEngine = Ruby.CreateEngine();
ScriptScope scriptScope = scriptEngine.CreateScope();
scriptScope.SetVariable("csharp", new BlockTestClass());
scriptEngine.Execute(scriptText, scriptScope);

The C# BlockTest class is:

public class BlockTestClass
{
    public void BlockTest(Func<string, string> block)
    {
        Console.WriteLine(block("hello "));
    }
}
A: 

I'm not sure of the complete answer, but you'll probably get farther if you call block.to_proc to convert it into a callable Proc object.

Paul Betts
A: 

I had no success with Func<>-Delegates and Blocks or Procs. Lazily, I used the Proc-Class in C# and called it from Ruby with block.to_proc. In C# you can then use Proc.Call and cast your result.

It's been a while so maybe this is out-dated.

Jabe
+1  A: 

A block is a mysterious thing that doesn't really exist in the CLR. Ruby does however have a proc which is analogous to CLR delegates.

For example, this should work fine:

C#:

public class SomeClass {
    public static void Run(Func<int, int> func) { ... }
}

Ruby:

proc = lambda { |i| i + 10 }
SomeClass.Run(proc)

There are several ways to create new procs (many people prefer Proc.new, but it doesn't quite work the same way as a C# function so I prefer lambda), but the key point is this:

lambda (and Proc.new) is a function - you pass the function a block, and it generates a new Proc object which wraps the block.

There is also a way of implicitly converting blocks to procs when they are passed to a function, using &.

Ruby:

def each
  yield 1
end

is the same as

def each(&block)
  block.call 1
end

The & takes the block which has been passed to the method (which would have otherwise been implicitly called by yield) and converts it into a Proc. Once it is a proc, you can pass it around, stick it in arrays, or pass it down to C#.

So, to call your C# method, you have two options:

  1. Create a wrapper method which takes the block, and uses & to convert it to a proc. You can then call the underlying C# method with this proc

  2. Use lambda or Proc.new in ruby to create a proc directly, and pass it to the C# method.

Examples:

C# Code (common):

public static class MyClass
{
    public static void Print(Func<int, int> converter)
    {
        Console.WriteLine("1: {0}", converter(1));
    }
}

Ruby Code 1 (wrapper):

class MyClass
  class << self # this is only needed to alias class (static) methods, not for instance methods
    alias_method :original_print, :print
  end

  def self.print(&block)
    original_print block
  end
end

MyClass.print {|i| i * 10}

Ruby Code 2 (direct):

MyClass.print lambda{|i| i * 10}
Orion Edwards
Wonderful, thx!
sipwiz