Search code examples
.netrubyironruby

Is there a way to set properties in IronRuby similar to C# object initializer syntax?


I am using IronRuby to do some scripting in my app.

I have lots of commands (CLR objects) I need to create and execute, but most of the commands need properties set on them and I want to avoid having to assign the command to a variable just so I can set the properties.

Is there a way to do it like the C# object initializer syntax?


Solution

  • There is no built-in way of setting properties when constructing CLR objects, as it is not a features of Ruby itself. However, though Python does not support this either, IronPython does support it by allowing named parameters to be supplied to constructors. Since Ruby does not support named parameters, we did not want to enforce a named parameter pattern, be it a Hash as the last argument, or a block which is instance_eval'd against the constructing object, or something else people come up with.

    That being said, both strategies of implementing object initializers can be written in pure Ruby without any special support in IronRuby. I'll describe both listed above, but feel free to experiment if they are not exactly the syntax you were looking for.

    Assuming the following CLR class (written in C#):

    namespace TestLib {
      public class TestObj1 {
        public string Prop1 { get; set; }
        public string Prop2 { get; set; }
      }
    }
    

    You could imagine initializing properties with a block passed to the constructor:

    testobj = TestObj1.new do
      self.prop1 = "Prop1 Value"
      self.prop2 = "Prop2 Value"
    end
    

    And here's how you could override the TestObj1.new method to support that:

    class TestObj1
      def self.new(*args, &block)
        obj = super(*args)
        obj.instance_eval &block
        obj
      end
    end
    

    Since this actually evals the block against the newly created object, you can execute any Ruby code inside the block. This pattern is popular when creating DSLs and more natural APIs in Ruby.

    Or, if you prefer using Ruby's lenient Hash syntax in method arguments:

    testobj = TestObj1.new :prop1 => "Prop1 value", :prop2 => "Prop2 value"
    

    Then this is how you could override the .new method:

    class TestObj1
      def self.new(*args)
        last_arg = args[-1]
        if last_arg.kind_of?(Hash)
          first_args = args[0..-2]
          obj = super(*first_args)
          last_arg.each do |prop, val|
            obj.send("#{prop}=", val)
          end
          return obj
        end
        super(*args)
      end
    end
    

    The Hash option is definitely a bit more complicated, but a bit more performant (as it avoids eval) and is a more common pattern in Ruby to expose named parameters.