Click here to Skip to main content
15,860,972 members
Articles / Programming Languages / Ruby

C# and Ruby Classes

Rate me:
Please Sign up or sign in to vote.
4.74/5 (16 votes)
23 Feb 2013CPOL16 min read 53.8K   27   15
A deep dive into the differences and similarities between C# and Ruby classes

(Apologies for the formatting of the tables - I'm working on improving the side-by-side comparisons.)

Contents

Introduction

Ruby is an interpreted object oriented language, as well as having characteristics similar to functional languages.  It's also known as a duck-typing language.  From wikipedia

In computer programming with object-oriented programming languages, duck typing is a style of dynamic typing in which an object's methods and properties determine the valid semantics, rather than its inheritance from a particular class or implementation of a specific interface. The name of the concept refers to the duck test, attributed to James Whitcomb Riley (see history below), which may be phrased as follows: 

When I see a bird that walks like a duck and swims like a duck and quacks like a duck, I call that bird a duck. 

In this article, I'm going to compare and contrast Ruby class definitions with C#.  There are already numerous articles on the subject, for example Brad Cunningham's blog post Ruby for the C# developer - The Basics.  What I'm endeavoring to accomplish here is a much more comprehensive discussion than one normally encounters in shorter blog entries.  To accomplish this, I've drawn from a variety of sources which I provide in the References section at the end of the article.  Throughout this article I also try to adhere to the correct naming conventions for C# and Ruby.  In Ruby, classes (which are considered constants) begin with a capital letter.  Everything else is lower case, and word groups for methods and attributes are separated by underscores, for example "MyClass" and "my_method".

Go to the head of the Class

"A class isn't just a definition, it's also a living object."5

Classes in both languages have similarities:

  • They define fields (usually non-public)
  • They define properties to access those fields
  • They define methods that take parameters and operate on those fields

However, there is considerable divergence with regards to access modifiers such as abstract, sealed, internal, and so forth.  These will be discussed first.

Defining an empty class in C# vs. Ruby:

C# Ruby
class CSharpClass
{
}
class RubyClass
end

The most notable difference here is the use of "end" instead of curly braces.

Derived Classes

The syntax for working with base classes and derived classes is similar:

C# Ruby
class BaseClass
{
}

class DerivedClass : BaseClass
{
}
class BaseClass
end

class DerivedClass < BaseClass
end

Constructors

Let's go over constructors first, as I will be using them when I illustrate some techniques for creating abstract classes in Ruby.  For the moment, let's assume public accessibility on the C# side.

C# Ruby
public class BaseClass
{
  public BaseClass()
  {
    Console.WriteLine("BaseClass constructor.");
  }
}

public class DerivedClass : BaseClass
{
  public DerivedClass()
  {
    Console.WriteLine("DerivedClass constructor.");
  }
}

class Program
{
  static void Main(string[] args)
  {
    new DerivedClass();
  }
}
class BaseClass
  def initialize
    puts "BaseClass initializer"
  end
end



class DerivedClass < BaseClass
  def initialize
    puts "DerivedClass initializer"
  end
end
DerivedClass.new

Base Class Constructors Are Not Automatically Called in Ruby

Observe the difference in the output with C# and Ruby (running Interactive Ruby Console):

C# Ruby

Image 1

Image 2

In Ruby, the base class constructor must be called explicitly using the "super" keyword:

class DerivedClass < BaseClass
  def initialize
    super
    puts "DerivedClass initializer"
  end
end

which results in the same behavior as with C#:

Image 3

Class Access Modifiers

C# supports the following access modifiers on a class:

  • public - the class can be accessed by any other file or assembly.  If omitted, the class behaves as if "internal" were specified.
  • sealed - prevents other classes from inheriting from a class with this access modifier.
  • internal - restricts accessibility to the class to within the files in the same assembly.

In Ruby, there is no equivalent - public, private and protected apply only to methods1.  As has been said about Ruby "In a duck-typing language, the focus is on methods, not on classes/types..."2

Class Type Modifiers

C# classes also allow two type modifiers:

  • static - indicates that a class contains only static fields, properties, and/or methods. 
  • abstract - indicates that a class is intended only to be a base class of other classes.  An abstract class cannot be instantiated directly.

In Ruby, there also are no direct equivalents to static and abstract.   However, a Ruby class can be coerced into looking like a static or abstract class, which we will explore next.

Static Classes: Creating a Static Class in Ruby

In Ruby, static variables are specified using a double-ampersand (@@) operator and static methods use the class name prepended to the function name:

C# Ruby
public static class StaticClass
{
  private static int foo = 1;

  public static int GetFoo()
  {
    return foo;
  }
}
class StaticClass
  @@foo = 1

  def StaticClass.get_foo
    @@foo
  end
end

Which, if get_foo is called, results in the expected behavior:

Image 4

Abstract Classes: Creating an Abstract Class by Raising an Initializer Exception

One approach is to raise an exception in the initializer (the equivalent of the constructor):

class AbstractRubyClass
  def initialize
    if self.class == AbstractRubyClass
      raise "Cannot instantiate an abstract class."
    end
    puts "AbstractRubyClass initializer"
  end
end

class ConcreteRubyClass < AbstractRubyClass
  def initialize
    super
    puts "ConcreteRubyClass initializer"
  end
end

We can now instantiate the concrete class the usual way:

ConcreteRubyClass.new

resulting in:

Image 5

However, if we try to instantiate the abstract class, we get an exception:

Image 6

This magic works because of the base class verifies that the class instance is not itself -- if it is, it raises the exception.  For other ways of creating abstract classes, see here.  One of the things about Ruby is that there is usually multiple ways of doing the same thing, and each has their pros and cons.

Creating a Sealed Singleton Class by Using the private_class_method Function

Having covered static and abstract type modifiers, it is seems appropriate to now take a look at singleton classes.  A singleton class can be created by changing the accessibility of the "new" function3, similar to how the constructor of a C# class is changed from public to protected or private:

C# Ruby
public class SingletonClass
{
  private SingletonClass() 
  { 
    Console.WriteLine("SingletonClass constructor.");
  }

  public static SingletonClass Create()
  {
    instance = instance ?? new SingletonClass();
    return instance;
  }
}
class SingletonClass
  private_class_method :new

  def initialize
    puts "SingletonClass initializer"
  end

  def SingletonClass.create(*args, &block)
    @@inst ||= new(*args, &block)
  end

end

Note that the above Ruby code is an example of a static method, which is covered at the end of this article.  Resulting in an error if we try to instantiate the class, but calling the initializer function once and only once when we use the static create method:

Image 7

For completeness, here's a brief explanation of some of the Ruby syntax:

  • *args - a parameter beginning with an asterisk (*) indicates a variable number of arguments
  • &block - this is essentially a lambda expression that can be passed in to the constructor
  • @@inst is a class variable (aka a static property of the class.)
  • ||= - this assigns the evaluated value on the right only when the l-value is nil
  • Similar to functional languages, the last computed value is automatically returned, hence no explicit return statement is required.

In C#, one can derive from a singleton class if the constructor is protected - for this reason, a C# singleton class' constructor should actually be marked private.  However, in Ruby, even though the private is actually more like C#'s protected accessibility, the above Ruby class cannot be derived.  For example, this:

class ConcreteRubyClass < SingletonClass
  def initialize
    super
    puts "ConcreteRubyClass initializer"
  end
end

Results in a runtime error:

Image 8

Several Things we Sometimes do with Classes

Robert Klemme has a excellent blog4 on features of a class that we all take for granted, from which the following four sections are derived.

Converting a Class To A String

All C# types, being derived from Object, implement ToString().  The equivalent in Ruby is "to_s":

C# Ruby
public abstract class Vehicle
{
}

public class Car : Vehicle
{
  public override string ToString()
  {
    return "Car";
  }
}

public class Boat : Vehicle
{
  public override string ToString()
  {
    return "Boat";
  }
}

class Program
{
  static void Main(string[] args)
  {
    Console.WriteLine(new Boat().ToString());
  }
}
# Abstract Vehicle class
class Vehicle
  def initialize
    if self.class == Vehicle
      raise "Cannot instantiate an abstract class."
    end
  end
end

class Car < Vehicle
  def initialize
    super
  end

  def to_s
    "Car"
  end
end

class Boat < Vehicle
  def initialize
    super
  end

  def to_s
    "Boat"
  end
end

Resulting of course in:

C# Ruby
Image 9
Image 10

Equivalence and Hash Codes

Ruby has several equality tests which can lead to confusion.6  The basic rule of thumb though is that the "equal?" function should never be overridden as it is used to determine object identity - object A and object B are the same instance.  Contrast this with C#, in which a warning message is issued if, when overriding ==, the Equals method is also not overridden.  Consider the C# and Ruby implementation for an "Engine" class in which different instances are considered equivalent if the number of cylinders is the same (yes, this is a contrived example):

C# Ruby
public class Engine
{
  public int Cylinders { get; set; }

  public Engine(int numCyl)
  {
    Cylinders = numCyl;
  }

  public static bool operator ==(Engine a, Engine b)
  {
    return a.Cylinders == b.Cylinders;
  }

  public static bool operator !=(Engine a, Engine b)
  {
    return !(a==b);
  }

  public override bool Equals(object obj)
  {
    Engine e = obj as Engine;

    if ((object)e == null)
    {
      return false;
    }

    return Cylinders == e.Cylinders;
  }
 
  public override int GetHashCode()
  {
    return base.GetHashCode() ^ Cylinders.GetHashCode();
  }
}

class Program
{
  static void Main(string[] args)
  {
    Engine e1 = new Engine(4);
    Engine e2 = new Engine(4);
    Engine e3 = new Engine(6);

    Console.WriteLine(e1 == e2);
    Console.WriteLine(e1 == e3);
    Console.WriteLine(e1.Equals(e2));
    Console.WriteLine((object)e1 == (object)e2);
  }
}
class Engine
  attr_accessor :numCyl

  def initialize(numCyl)
    @numCyl = numCyl
  end

  def ==(other)
    self.class.equal?(other.class) &&
      @numCyl==other.numCyl
  end

  def hash
    super.hash ^ numCyl
  end
end

e1 = Engine.new(4)
e2 = Engine.new(4)
e3 = Engine.new(6)

e1==e2
e1==e3
e1.Equal?(e2)

resulting in the output:

C# Ruby
Image 11
Image 12

Notice in C# that in order to perform instance comparison, the instances must be cast to objects so as not to use the overridden == operator, and furthermore, the Equals operator can no longer be used to test for different instances.

Notice in the Ruby code that a test for the same type is made, because in Ruby, we can pass in objects of different types and, if it had a numCyl attribute, the code would happily compare to disparate objects.

Comparability

Let's take a quick look at comparison operators.7  The comparison operators <., <=, >=, > each require a separate override in C# which is much simpler in Ruby:

C# Ruby
public class Engine
{
  ...
  public static bool operator <(Engine a, Engine b)
  {
    return a.Cylinders < b.Cylinders;
  }

  public static bool operator <=(Engine a, Engine b)
  {
    return a.Cylinders <= b.Cylinders;
  }

  public static bool operator >(Engine a, Engine b)
  {
    return a.Cylinders > b.Cylinders;
  }

  public static bool operator >=(Engine a, Engine b)
  {
    return a.Cylinders >= b.Cylinders;
  }
...
}
class Engine
  include Comparable
  ...
  def <=>(other)
    self.class == other.class ? 
        @numCyl <=> other.numCyl : nil
  end
end

The Ruby code takes advantage of something called a "mixin" (more on that later) and the <=> operator.

Dispose and Finalizers

Both C# and Ruby use automatic garbage collection rather than requiring the programmer to make explicit de-allocation calls.  This is fine until one has to free up unmanaged resources.  C# provides a couple mechanisms for doing this, the most common of which is to wrap the code that is using an unmanaged resource in a "using" block--when the block exists, the object's destructor is automatically called, assuming the class implements the IDispose interface, a correct implementation10 in C# and a correct implementation11 of the finalizer and a hackish implementation of dipose (by me, borrowing13 from some example code regarding "using") in Ruby looks like this:

C# Ruby
public class DisposeMe : IDisposable
{
  private bool disposed = false;

  public DisposeMe()
  {
    Console.WriteLine("Constructor");
  }

  ~DisposeMe()
  {
    Console.WriteLine("Destructor");
    Dispose(false);
  }

  public void Dispose()
  {
    Console.WriteLine("Dispose");
    Dispose(true);
    GC.SuppressFinalize(this);
  }

  protected virtual void Dispose(bool disposing)
  {
    Console.WriteLine("Disposing. disposed = {0}, disposing = {1}",
      disposed, disposing);

    if (!disposed)
    {
      if (disposing)
      {
        Console.WriteLine("Free other managed resources");
      }

      Console.WriteLine("Free unmanaged resources");
      disposed = true;
    }
  }
}
# global method - needs to be defined only once somewhere
def using(object)
  yield object
ensure
  object.dispose
end

class DisposeMe
  @disposed = false

  def initialize
    ObjectSpace.define_finalizer(self, self.class.finalize(@disposed))
  end

  # pass in other instance specific params that are needed
  # to dispose of unmanaged objects.
  def self.finalize(disposed)
    proc {
      if !disposed
        dispose #pass in other args here
      else
        puts "object already disposed"
      end
    }
  end

  # class method called by GC. Pass in other required instance
  # variables here to dispose of unmanaged objects.
  def self.dispose
    puts "Disposing unmanaged objects"
  end

  # instance method called by using. We have access to instance
  # variables here
  def dispose
    self.class.dispose
    @disposed = false
  end
end

Note how in the Ruby code we need both a class and an instance method "dispose" because the finalizer's proc cannot reference its own instance--the object will never be collected otherwise.

A simple test program:

C# Ruby
class Program
{
  static void Main(string[] args)
  {
    Console.WriteLine("Begin");
    
    using (DisposeMe d = new DisposeMe())
    {
      Console.WriteLine("Doing something with DisposeMe");
    }

    Console.WriteLine("End");
  }
}
using(DisposeMe.new) do |my_obj| puts "Doing something."; end

# simulate GC after using has disposed object
DisposeMe.finalize(true).call

# simulate GC when object hasn't been disposed
DisposeMe.finalize(false).call

yields (hahaha):

C# Ruby
Image 13 Image 14

Property Assignment During Construction

A sometimes useful syntax in C# is to initialize properties during the class instantiation, but not as parameters passed to the constructor.  If you want to do this in Ruby, you can simply add a block to the constructor

C# Ruby
public class TwoNums
{
  public int A { get; set; }
  public int B { get; set; }

  public int Sum { get { return A + B; } }
}

class Program
{
  static void Main(string[] args)
  {
    TwoNums nums = new TwoNums() { A = 1, B = 5 };
    Console.WriteLine(nums.Sum);
  }
}  
class Adder
  attr_accessor :a
  attr_accessor :b
  
  def sum
    a+b
  end

  def initialize(&block)
    block.call(self) unless block.nil? 
  end
end

# Example usage:

adder = Adder.new do |this| this.a=1; this.b=5 end
adder.sum
adder2 = Adder.new

However, this requires that you create constructors specifically with the ability to call a block (an expression).  A significantly more complicated15 solution involves redefining the "new" function of a class, which I won't get into here, and requires that the "initializable attributes" be declared so that the new function can determine which arguments are initializer arguments and which are constructor arguments.

Interfaces

A discussion of classes would not be complete without looking at the concept of interfaces in C# and Ruby.  In C#, an interface enforces a kind of contract: class C, inheriting from interface I, will implement all the properties and methods defined in I.  In Ruby, interfaces are meaningless because methods can be added and removed dynamically from classes and even if they are missing from a class, they can be handled in the "method_missing" hook that Ruby provides.8  The dynamic nature of Ruby makes it impossible to enforce an implementation contract (an interface.) 

Multiple Inheritance (mix-ins and modules)

C# does not support multiple inheritance at all.  Interfaces are not really a way to implement multiple inheritance because the interface is merely a definition, not an implementation.  Ruby does not explicitly support multiple inheritance in a formal syntax (like "Car < Vehicle, OperatorLicense") however the principles behind multiple inheritance (inheriting implementation from multiple source) is achieved with modules and mix-ins9.  For example:

require "date"
# Abstract Vehicle class
class Vehicle
  def initialize
    if self.class == Vehicle
      raise "Cannot instantiate an abstract class."
    end
  end
end

module OperatorLicense
  attr_accessor :licenseNo
  attr_accessor :expirationDate
end

class Car < Vehicle
  include OperatorLicense
  def initialize
    super
  end

  def to_s
    "#{licenseNo} expires #{expirationDate.strftime("%m/%d/%Y")}"
  end
end

By "mixing in" the module "OperatorLicense", we acquire the attributes (aka properties) "licenseNo" and "expirationDate", which we can now use in the Car subclass:

car = Car.new
car.licenseNo = "1XYZ30"
car.expirationDate=DateTime.new(2016, 12, 31)

resulting in:

Image 15

This is a powerful way to extend the behavior to Ruby classes, and you will see this used frequently (for example, Comparable used above is a mix-in.)

Fields and Properties

Properties in C# are back by either explicit or implicit fields, which are termed "attributes" in Ruby. 

Fields

In the following example, the use of fields is equivalent between C# and Ruby:

C# Ruby
public class Person
{
  private string firstName;
  private string lastName;

  public Person(string first, string last)
  {
    firstName = first;
    lastName = last;
  }

  public string FullName()
  {
    return firstName + " " + lastName;
  }
}
class Person
  def initialize(first, last)
    @first_name = first
    @last_name = last
  end

  def full_name
    @first_name + " " + @last_name
  end
end

Of course, note that in Ruby one does not need to declare fields.

Properties

Similarly, properties are methods in C# and Ruby.  If we avoid the syntactical sugar in both languages, properties are declared thus:

C# Ruby
public class Person
{
  private string firstName;
  private string lastName;

  public string get_firstName() {return firstName;}
  public void set_firstName(string name) {firstName=name;}

  public string get_lastName() { return lastName; }
  public void set_lastName(string name) { lastName = name; }

  public Person(string first, string last)
  {
    firstName = first;
    lastName = last;
  }

  public string FullName()
  {
    return firstName + " " + lastName;
  }
}
class Person
  def first_name
    @first_name
  end

  def first_name=(name)
    @first_name=name
  end

  def last_name
    @last_name
  end

  def last_name=(name)
    @last_name=name
  end

  def initialize(first, last)
    @first_name = first
    @last_name = last
  end

  def full_name
    @first_name + " " + @last_name
  end
end

This is obviously not the syntax typically used in either language.  What we normally see is something more like:

C# Ruby
public class Person
{
  public string FirstName { get; set; }
  public string LastName { get; set; }

  public Person(string first, string last)
  {
    FirstName = first;
    LastName = last;
  }

  public string FullName()
  {
    return FirstName + " " + LastName;
  }
}
class Person
  attr_accessor :first_name
  attr_accessor :last_name

  def initialize(first, last)
    @first_name = first
    @last_name = last
  end

  def full_name
    @first_name + " " + @last_name
  end
end

The usage is what we would expect:

C# Ruby
Person p = new Person("Marc", "Clifton");
Console.WriteLine(p.FullName());
p.FirstName = "Ian";
Console.WriteLine(p.FullName());
p = Person.new("Marc", "Clifton")
p.full_name
p.first_name="Ian"
p.full_name

Read-Only Properties

A common form in C# is to demark the setter as protected so that it is not writeable (this is different from a "readonly" attribute).  A similar concept exists in Ruby:

C# Ruby
public string FirstName { get; protected set; }
public string LastName { get; protected set; }
attr_reader :first_name
attr_reader :last_name

And in Ruby, attributes with the same access level can be grouped together, for example:

attr_reader :first_name, :last_name

Also, in Ruby, there is an "attr_writer", equivalent in C# to the following:

C# Ruby
public string FirstName { protected get; set; }
public string LastName { protected get; set; }
attr_writer :first_name
attr_writer :last_name

And in fact, attr_accessor in Ruby is the equivalent of declaring attr_reader and attr_writer.

Read Only Fields

The C# readonly attribute can be implemented to prevent assignment to fields.  There is no equivalent in Ruby -- do not confuse the "freeze" function with C#'s readonly attribute.  Ruby's freeze function prevents an object from being modified, it does not prevent the same object from being assigned a new value.

C# Ruby
public class ReadOnlyExample
{
  // Can be initialized in the declaration.
  public readonly int SomeValue = 13;

  public ReadOnlyExample()
  {
    // Can be assigned in the constructor
    SomeValue = 15;
  }

  public void AttemptToModify()
  {
    // but no further assignments are allowed.
    SomeValue = 3;
  }
}
class ReadOnlyExample
  attr_accessor :some_value

  def initialize
    @some_value = 15
    @some_value.freeze
  end

  def attempt_to_modify
    @some_value = 3
  end
end

a = ReadOnlyExample.new
a.attempt_to_modify
a.some_value

Resulting in the compiler error in C#:

error CS0191: A readonly field cannot be assigned to (except in a constructor or a variable initializer)

but results in a perfectly valid execution in Ruby:

Image 16

Static Properties (Class Properties)

The above describes "instance properties" and in both languages there is support for static properties, otherwise known as class variables in Ruby.  In the following example, I've added a static property to count the instantiations of the Person type:

C# Ruby
public class Person
{
  public static int TotalPeople { get; set; }
  public string FirstName { protected get; set; }
  public string LastName { protected get; set; }

  public Person(string first, string last)
  {
    FirstName = first;
    LastName = last;
    ++TotalPeople;
  }

  public string FullName()
  {
    return FirstName + " " + LastName;
  }
}

class Program
{
  static void Main(string[] args)
  {
    Person p1 = new Person("Marc", "Clifton");
    Person p2 = new Person("Ian", "Clifton");
    Console.WriteLine(Person.TotalPeople);
  }
}
class Person
  @@total_people = 0
  attr_accessor :first_name, :last_name

  def self.total_people
    @@total_people
  end

  def initialize(first, last)
    @first_name = first
    @last_name = last
    @@total_people += 1
  end

  def full_name
    @first_name + " " + @last_name
  end
end

p1 = Person.new("Marc", "Clifton")
p2 = Person.new("Ian", "Clifton")
Person.total_people
#also
Person::total_people

Note the "self." in the function "total_people", which indicates that the function is association with the class Person rather than the instance p1 and p2.  The Ruby code could also have been written as:

def Person.total_people
  @@total_people
end

However, "self" is becoming more in vogue.  Also, consider this syntax:

class Person
  class << self
    attr_accessor :total_people
  end

  def initialize(first, last)
    @first_name = first
    @last_name = last
    Person.total_people ||= 0
    Person.total_people += 1
  end

  def full_name
    @first_name + " " + @last_name
  end
end

p1 = Person.new("Marc", "Clifton")
p2 = Person.new("Ian", "Clifton")
Person.total_people

Here, the syntax "class << self" is a singleton declaration, specifying that attributes and methods belong to the class definition rather than the class instance and can be accessed with the "Person." construct ("self." no longer works).

Writing Your Own Accessor

And in Ruby, we could just write our own "attr_static_accessor"12 by adding the definition for "attr_static_accessor" to the Class type:

class Class
  def attr_static_accessor(class_name, *args)
    args.each do |arg|
      self.class_eval("def #{class_name}.#{arg};@@#{arg};end")
      self.class_eval("def #{class_name}.#{arg}=(val);@@#{arg}=val;end")
    end
  end
end

class Person
  attr_static_accessor(:Person, :total_people)

  def initialize(first, last)
    @first_name = first
    @last_name = last
    @@total_people ||= 0
    @@total_people += 1
  end

  def full_name
    @first_name + " " + @last_name
  end
end

p1 = Person.new("Marc", "Clifton")
p2 = Person.new("Ian", "Clifton")
Person.total_people

which results in the expected behavior:

Image 17

Computational Getters and Setters

In C#, computations are often placed into property getters and setters.  Obviously, in Ruby, one can just write the functions:

C# Ruby
public class Person
{
  public string FirstName { get; protected set; }
  public string LastName { get; protected set; }

  public string FullName
  {
    get { return FirstName + " " + LastName; }
    set
    {
      string[] names = value.Split(' ');
      FirstName = names[0];
      LastName = names[1];
    }
  }
}

class Program
{
  static void Main(string[] args)
  {
    Person p = new Person() {FullName = "Marc Clifton"};
    Console.WriteLine(p.FirstName);
    Console.WriteLine(p.LastName);
    Console.WriteLine(p.FullName);
  }
}
class Person
  attr_accessor :first_name, :last_name

  def full_name=(value)
    names = value.split(" ")
    @first_name=names[0]
    @last_name=names[1]
  end

  def full_name
    @first_name + " " + @last_name
  end
end

p = Person.new
p.full_name = "Marc Clifton"
p.first_name
p.last_name
p.full_name

Resulting in:

C# Ruby
Image 18
Image 19

However, if you want to get fancy, one can do the equivalent in Ruby by passing the expression in as a string and using custom attribute accessors.  First we define a "attr_computed_accessor" which will be available to all classes.

class Class
  def attr_computational_accessor(props)
    props.each { |prop, fnc|
      if fnc.key?(:getter)
        self.class_eval("def #{prop};#{fnc[:getter]};end")
      end
      if fnc.key?(:setter)
        self.class_eval("def #{prop}=(value);#{fnc[:setter]};end")
      end
    }
  end
end

Now we can write a test class:

class ComputationalProperty
  #expose attributes set by computation
  attr_accessor :first_name
  attr_accessor :last_name

  attr_computational_accessor(:full_name => {
    :getter => %{@first_name + " " + @last_name},
    :setter => %{
      names=value.split(" ")
      @first_name = names[0]
      @last_name = names[1]
      }
  })
end

Note that the above code, while it looks like a lambda expression, actually isn't -- it's a string being passed to the class_eval function.

Here's the result:

Image 20

If you want to use lambda expressions, you have to pass in the context14.  This is how the attribute is defined:

class Class
  def attr_computational_accessor(props)
    props.each { |prop, fnc|
      if fnc.key?(:getter)
        self.class_eval do
          define_method prop do
            instance_eval do fnc[:getter].call(self) end
          end
        end
      end
      if fnc.key?(:setter)
        self.class_eval do
          define_method "#{prop}=".to_sym do |value|
            instance_eval do fnc[:setter].call(self, value) end
          end
        end
      end
    }
  end
end

requiring that the context be specified to access instance attributes and methods, which I represent with the parameter "this":

class ComputationalProperty
  #exposes attributes set by computation
  attr_accessor :first_name
  attr_accessor :last_name
  attr_computational_accessor(:full_name => {
    :getter => lambda {|this| this.first_name + " " + this.last_name},
    :setter => lambda {|this, value|
      names = value.split(" ")
      this.first_name = names[0]
      this.last_name = names[1]
    }
  })
end

Here's one more usage example, illustrating just  a getter:

class AnotherComputer
  attr_accessor :a
  attr_accessor :b
  attr_computational_accessor(:sum => {:getter => lambda {|this| this.a + this.b}})
end

Example usage:

Image 21

Events

Another common practice in C# is to provide events for when property values change.  For example, here we have a class that fires a "changing" event, providing the event handler with both the old and new values.  As a comparison with Ruby, I am leveraging the Ruby gem "ruby_events"16:

C# Ruby
public class PropertyChangingEventArgs<T> : EventArgs
{
  public T OldValue { get; set; }
  public T NewValue { get; set; }
}

public delegate void PropertyChangingDlgt<T>(object sender, 
                         PropertyChangingEventArgs<T> args);

public class Things
{
  public event PropertyChangingDlgt<int> AChanging;
  private int a;

  public int A
  {
    get { return a; }
    set
    {
      if (a != value)
      {
        if (AChanging != null)
        {
          AChanging(this, new PropertyChangingEventArgs<int>() 
             { OldValue = a, NewValue = value });
        }

        a = value;
      }
    }
  }
}
require 'rubygems'
require 'ruby_events'

class Things
  def A
    @a
  end

  def A=(value)
    if (value != @a)
      events.fire(:a_changing, self, @a, value)
    end

    @a=value
  end
end

Note that I just broke the naming convention style in the Ruby code -- the "A" function should actually be "a".

And to test:

C# Ruby
class Program
{
  static void Main(string[] args)
  {
    Things things = new Things() { A = 2 };

    things.AChanging += (sender, changeArgs) => 
    { 
      Console.WriteLine("Old Value = {0}, New Value = {1}",
             changeArgs.OldValue, changeArgs.NewValue);
    };

   things.A=5;
  }
}
Image 22

That was easy enough.  There are many other ways to do this as well17 but one of the interesting features of the ruby_events gem is that you can inject callbacks into existing class methods.  For example, the property setter doesn't need to explicitly raise an event:

class OtherThings
  attr_accessor :b
end

o = OtherThings.new
o.b = 2
o.events.fire_on_method(:b=.to_sym, :injected)
o.events.listen(:injected) do |event_data| puts "b is now " + event_data.to_s end
o.b = 5
b is now 5
=> 5

Now what could be cooler than that!

Methods

We've already seen a variety of methods--property getters, setters, and some simple functions.  There are a couple other concepts to cover here.

Static Methods

Static methods are prefixed either with "self." or with the class name.  For example, these are equivalent:

class StaticMethods
  def self.first_static_method
    puts "first"
  end

  def StaticMethods.second_static_method
    puts "second"
  end
end

Usage is just like C#:

Image 23

Abstract Methods

If you want to enforce abstract behavior you can define a class-level function that raises an exception if accidentally called, thus enforcing implementation in the derived class (of course, because Ruby is interpreted, this occurs at runtime.)

class Class
  def abstract(*methods)
    methods.each do |m| define_method(m) {raise "Abstract method #{m} called."} end
  end
end

which is used thus:

class AbstractMethods
  abstract :A1, :A2
end

class ConcreteMethods < AbstractMethods
  def A1
    puts "A1"
  end

  # oops, didn't implement A2
end

Resulting in the following output:

Image 24

Method Missing

"All RubyEvents functionality is just an object.events call away."16

Yes, methods are actually messages, which opens the door to creating new behaviors when a method to a class is missing.  So let's use the method missing feature to convert Roman numerals to their Arabic equivalent (heavily borrowed code 19):

class RomanConverter
  @@data = [
    ["M" , 1000],
    ["CM" , 900],
    ["D" , 500],
    ["CD" , 400],
    ["C" , 100],
    ["XC" , 90],
    ["L" , 50],
    ["XL" , 40],
    ["X" , 10],
    ["IX" , 9],
    ["V" , 5],
    ["IV" , 4],
    ["I" , 1]
  ]

  def self.to_arabic(rom)
    result = 0
    for key, value in @data
      while rom.index(key) == 0
        result += value
        rom.slice!(key)
      end
    end
  result
end

  def self.method_missing(sym, *args)
    # no validation!
    to_arabic(sym.to_s)
  end
end

Now we can use Roman numerals as method names and return the Arabic number:

Image 25

Testing

In essence, the problem is that, "if it walks like a duck and quacks like a duck", it could be a dragon doing a duck impersonation. You may not always want to let dragons into a pond, even if they can impersonate a duck.

Proponents of duck typing, such as Guido van Rossum, argue that the issue is handled by testing, and the necessary knowledge of the codebase required to maintain it. (Duck Typing

If you read through any of the sections in this article, you will probably be very quickly struck by the flexibility of Ruby and the fact that, being a duck-typed language, there is no compile-time type checking -- if there are type violations, missing implementation, and so forth, those may be discovered when you run the program--if the dragon can quack, it's still not a duck.  Furthermore, the amazing meta-programming capabilities18 of Ruby also won't reveal issues until runtime.  For this reason, the need for writing tests cannot be overstated.  As you develop a Ruby application, tests should be incrementally and immediately added for each new behavior and change to existing behaviors.  If you fail to do this, you will most likely encounter runtime errors months later because a particular code branch broke that relied on something that no longer exists or works differently.  Two popular testing tools are RSpec and Cucumber.

References

1 - http://rubylearning.com/satishtalim/ruby_access_control.html
2 - http://stackoverflow.com/questions/512466/how-to-implement-an-abstract-class-in-ruby
3 - http://en.wikibooks.org/wiki/Ruby_Programming/Syntax/Classes
4 - http://blog.rubybestpractices.com/posts/rklemme/018-Complete_Class.html
5 - http://openmymind.net/2010/6/25/Learning-Ruby-class-self/
6 - http://stackoverflow.com/questions/7156955/whats-the-difference-between-equal-eql-and
7 - http://blog.rubybestpractices.com/posts/rklemme/018-Complete_Class.html
8 - http://briancarper.net/blog/226/
9 - http://www.ruby-doc.org/docs/ProgrammingRuby/html/tut_modules.html
10 - http://lostechies.com/chrispatterson/2012/11/29/idisposable-done-right/
11 - http://www.mikeperham.com/2010/02/24/the-trouble-with-ruby-finalizers/
12 - http://mikeyhogarth.wordpress.com/2011/12/01/creating-your-own-attr_accessor-in-ruby/
13 - http://usrbinruby.net/ruby-general/2010-01/msg01369.html
14 - http://stackoverflow.com/questions/7470508/ruby-lambda-context
15 - http://stackoverflow.com/questions/1077949/best-way-to-abstract-initializing-attributes
16 - https://github.com/nathankleyn/ruby_events
17 - http://thatextramile.be/blog/2010/08/using-c-style-events-in-ruby
18 - http://yehudakatz.com/2009/11/15/metaprogramming-in-ruby-its-all-about-the-self/
19 - http://www.rubyquiz.com/quiz22.html

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Architect Interacx
United States United States
Blog: https://marcclifton.wordpress.com/
Home Page: http://www.marcclifton.com
Research: http://www.higherorderprogramming.com/
GitHub: https://github.com/cliftonm

All my life I have been passionate about architecture / software design, as this is the cornerstone to a maintainable and extensible application. As such, I have enjoyed exploring some crazy ideas and discovering that they are not so crazy after all. I also love writing about my ideas and seeing the community response. As a consultant, I've enjoyed working in a wide range of industries such as aerospace, boatyard management, remote sensing, emergency services / data management, and casino operations. I've done a variety of pro-bono work non-profit organizations related to nature conservancy, drug recovery and women's health.

Comments and Discussions

 
QuestionGreat article! ...but what about best practices? Pin
MirageCoder8-Aug-13 6:30
MirageCoder8-Aug-13 6:30 
AnswerRe: Great article! ...but what about best practices? Pin
Marc Clifton8-Aug-13 7:42
mvaMarc Clifton8-Aug-13 7:42 
Newswonderfual jobs. Pin
roger.wang_16-May-13 20:38
professionalroger.wang_16-May-13 20:38 
QuestionWhat would extend the usefulness of this article to me Pin
BillWoodruff10-Apr-13 7:30
professionalBillWoodruff10-Apr-13 7:30 
AnswerRe: What would extend the usefulness of this article to me Pin
Marc Clifton10-Apr-13 14:20
mvaMarc Clifton10-Apr-13 14:20 
Questionminor formatting issue reading this article with Chrome Version 26.0.1410.64 m Pin
BillWoodruff10-Apr-13 7:08
professionalBillWoodruff10-Apr-13 7:08 
GeneralMy vote of 5 Pin
jim lahey20-Mar-13 5:48
jim lahey20-Mar-13 5:48 
SuggestionInstance comparison... Pin
erzengel.des.lichtes5-Mar-13 9:28
erzengel.des.lichtes5-Mar-13 9:28 
GeneralRe: Instance comparison... Pin
Marc Clifton7-Mar-13 4:25
mvaMarc Clifton7-Mar-13 4:25 
GeneralMy vote of 5 Pin
SleepyCrat26-Feb-13 5:28
SleepyCrat26-Feb-13 5:28 
GeneralMy vote of 4 Pin
Paulo Zemek24-Feb-13 9:23
mvaPaulo Zemek24-Feb-13 9:23 
GeneralRe: My vote of 4 Pin
Marc Clifton25-Feb-13 2:15
mvaMarc Clifton25-Feb-13 2:15 
GeneralRe: My vote of 4 Pin
Paulo Zemek25-Feb-13 3:02
mvaPaulo Zemek25-Feb-13 3:02 
GeneralRe: My vote of 4 Pin
Marc Clifton25-Feb-13 3:27
mvaMarc Clifton25-Feb-13 3:27 
GeneralMy vote of 5 Pin
ring_024-Feb-13 0:41
ring_024-Feb-13 0:41 
Just Right on Time! Could not give you more than 5.
Thanks.

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.