IDisposable in Ruby

over 7 years ago

I have been learning Ruby lately and have been very impressed with a language feature called code blocks.

Essentially it allows any method to return control and optionally data to the caller. It is used to implement iterators, transactions, profiling blocks etc...

So, I decided to see if it could be applied to the problem IDisposable was created to solve.

Take the following C# code:

class Tester : IDisposable
{
   public Tester()
   {
       Console.WriteLine("Allocating");
   } 

   public void DoStuff()
   {
      Console.WriteLine("doing stuff");
   } 

   public void Dispose()
   {
      Console.WriteLine("Deallocating");
   }  

class Program
{ 
   static void Main(string[] args)
   {
      try 
      {
          using (Tester t = new Tester())
          {
                t.DoStuff()              
                throw new Exception("something happened");
          }
      catch (Exception e)
      { 
         Console.WriteLine(e.ToString());
      }
   }
}

This simple example has a pile of issues.

  • The designer of the "Tester" class has no way of ensuring that "Tester" objects are wrapped in using blocks (or at least always finally disposed)

  • This can be partially worked around by defining a finalizer, tracking weather the object is disposed or not and suppressing the finalizer if the object is disposed properly. It is a real headache.

  • The designer of the "Tester" object has no way of telling if the object was used successfully and no exceptions were thrown.

  • .NET has no mechanism for asking the framework if exception handling blocks are currently being executed.

I rewrote it in Ruby:

class Tester

   def do_stuff()
      puts "doing stuff"
   end

   def initialize()
      raise "Must be called from a code block!" if !block_given?
      begin 
         puts "Allocating resources" 
         yield self
         rescue
            puts "An exception happend: " + $!
            raise 
         ensure
            puts "Deallocating resources"
         end 
      end
   end
end  

begin 
   Tester.new do |test| 
      test.do_stuff 
      raise "Something happened!"
   end 
   rescue
      puts "Caught: " + $! 
   end 
end

Ruby's advantages:

  • You can trap and track exceptions during the cleanup phase.
  • You can ensure proper usage. (Raises an exception if not used from a code block
  • You need to write less code.
  • It is a lot less mysterious. For someone new to .Net the relationship between the "using" keyword and the IDisposable interface may seem arbitrary.

Comments


comments powered by Discourse