Search code examples
c#ironruby

Use an IronRuby wrapper method to get a block into C#


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 "));
    }
}

Solution

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