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 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:
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
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}