devxlogo

Ruby for C# Geeks

Ruby for C# Geeks

ttention C# developers: C# is good for a great many things, but it’s not the best language for everything. In fact, there is no such thing as “the best language.” It all depends on what you’re trying to do, and your personal preference and familiarity with the languages you have available. Although the Church-Turing Thesis suggests that anything you can do in one complete language, you can also do in another, the truth is not all languages are created equal. You can accomplish simple tasks with complex code in some languages and complex tasks with simple code in others. C# sometimes falls into both categories, but more often than not, it requires a bit more than its fair share of effort. Strangely enough, a language that allows you to accomplish simple tasks with simple code is rare. Enter Ruby, an interpreted, dynamically typed language that enables just that.

Many would say that the main difference between Ruby and C# is that Ruby is a dynamic language whereas C# isn’t. However, referring to C# as a static language really isn’t right because you wouldn’t apply that term to an entire language as you would to one of the dynamic variety. Ruby really differs from C# in that its code is not actually compiled into an intermediate executable form before it is run. Instead, Ruby has at its heart a text-driven interpreter. This means that the expressions and statements in a Ruby program are evaluated as the interpreter passes over them. In C#, you must first compile the code to an .exe or .dll file to be able to run it.

In requiring compilation, C# encapsulates an opportunity to check syntax and optimize the runtime efficiency of the code before it’s ever run. On the other hand, all of the declaration and specification that goes into setting up your code with all of the necessary information to allow the compiler to perform these tricks will slow you down when these features aren’t necessary or desired. You might use a language like Ruby, with looser guidelines, to test your algorithmic theories or rapidly prototype an application. Sometimes you just need to format a couple of text files, and C# isn’t exactly friendly in cases where you just want to automate something as simple as a command line.

This article offers a brief introduction to the Ruby language from the perspective of a C# developer. You’ll learn the differences between the languages’ features through line-by-line examinations of identical programs built in each one.

Same Results, Simpler Code
Every programming language tutorial I’ve ever read has what is (often sardonically) known as the “obligatory Hello World! example.” I’m not very fond of that terminology, so I’ve spruced up the sample application for this article to the “slightly interesting Hello Foo! example.” The snippets to follow show two programs (one in C#, the other in Ruby) that produce exactly the same output for my Hello Foo! example.

First, here’s the C# version:

// Hello Foo! in C# 

using System ;namespace TestCSharp{ class MainClass { public static void Main(string[] args) { Console.WriteLine("Hello Foo!"); //say hello } }}

As you can see, C# requires you to specify a lot of structure and actually tell the compiler that you’re going to write a class. Specifically, here you tell it you’re going to write a class with a well-known method called Main that a console application will use as the first method to call (known as an entry point) when the program executes. The required verbosity is somewhat mitigated by Visual Studio or any other syntax-helping editor that allows for templates and IntelliSense; however, the level of code complexity is still there regardless of whether or not you have to actually type every letter.

First, compile the C# program (If you’re using Visual Studio or MonoDevelop, you can simply press F5.), and then you can run it:

# Hello Foo! in Rubyputs "Hello Foo!" # say hello

Running this little snippet requires that you simply invoke the interpreter and then provide the name of the script file (hellofoo.rb):

C:>ruby hellofoo.rbHello Foo! C:>

Since the Ruby interpreter assumes all of the structural information is there, you don’t have to write it like you would in C#. The interpreter assumes that the first bunch of code you write without an enclosing class or module declaration is analogous to it appearing within the Main method of the entry point class. Very handy.

Note the differences in syntax between the languages as well:

  • Semi-colons aren’t used to delimit the end of a statement in Ruby.
  • Ruby’s built-in puts method is actually like C#’s Console.Writeline in that it will display the result of the .to_s method of the parameter object, which in C# you would write as .ToString().
  • Parentheses are optional for method calls in Ruby. Most of the time, you simply omit them?particularly when you don’t pass any parameters.
  • The // style of commenting in C# is replaced by the # notation in Ruby. Authors Note: Because its syntax is regarded as self-explanatory, a common belief is that Ruby rarely requires comments. I’m a little skeptical of this idea, but I will admit that it’s often notably easier to understand Ruby code as a human reader than it is the C# equivalent.

Dynamically Typed Means Faster Coding
Not only is Ruby an interpreted (i.e., dynamically evaluated) language , it is also dynamically typed. So the variables in Ruby do not require the specification of their types before you use them. The interpreter will infer variable types as they are assigned values. To add another twist, you don’t have to declare variables at all! This is a common feature of many interpreted languages, and even a few compiled languages. To see what I’m talking about, consider the following Ruby snippet:

#Rubyabc = 1  #abc is a Fixnum (integer)puts abcabc = "Rubber Baby Buggy Bumpers"  # now it's a string!puts abc

You can see that you have to declare neither the variables nor their types in Ruby, and you can change the type by assigning it a different value?right in the middle of the running program. Very dynamic! If you tried to do something like that in C#, the program wouldn’t even compile. C# requires statically defining types for variables, which allows the compiler to catch any errors that may arise when types don’t match the ways in which they are used. However, it’s faster and easier to throw together a script that doesn’t go to all of this trouble in Ruby. Both approaches may have a time and a place, but you can choose the one that best suits the problem you’re trying to solve.

The Ruby vs. C# Feature Rundown
The following is a simple example program in Ruby that demonstrates a variety of the features often seen in C# programs. (It’s another “classic” example program used in many programming tutorials.) It is chock full of new stuff I haven’t discussed yet, but I’ll explain the entire program snippet by snippet afterwards. If it’s still opaque after that, fear not because I provide a structurally similar C# version so you can compare lines for yourself:

#gessnum.rbclass Game   def initialize(maxNum)      @num = rand(maxNum)      puts ["

Magic Number Game!
",           "------------------

",              "I'm thinking of a magic number between 0 and #{maxNum}
",             "Would you like to guess my number? (y/n) [n]:"]      playNow = gets or "n"      if playNow.chop == "y"         play       else         puts "OK, Bye!"      end   end         def play      loop do # infinite loop!               puts "
Number?!?"         attempt = gets or break                  case attempt.to_i <=> @num            when 1 # attempt > @num               puts "Guess Lower!"               when -1 # attempt < @num               puts "Think Bigger, Mac."            else # only have == left... so...               puts "Spot on! That's it! Bye!"               break # hop out of our loop!         end      end   endendGame.new(100)

Here's a sample run:

C:proj
uby>ruby sample.rbMagic Number Game!------------------I'm thinking of a magic number between 0 and 100Would you like to guess my number? (y/n) [n]:yNumber?!?50Guess Lower!Number?!?25Think Bigger, Mac.Number?!?37Spot on! That's it! Bye!

At the very beginning of the code, I define a class. Notice how it's just the word "class" followed by the class name?no curly anything, and I haven't set a namespace.

The Game class contains two methods: initialize, the constructor, and play, the main body of the game. Methods are designated simply by the def keyword, and are terminated by the end keyword. In Ruby, all constructors are called initialize, and they are called when an object is instantiated (more on this in a minute).

The following snippet designated a variable called @num in the constructor. The variable was set to a random number between 0 and maxNum:

@num = rand(maxNum)

Any variable inside a class that starts with a @ sign is known as an instance variable (like a field in C#). Just like in C#, the default for an instance variable is to be private, meaning that folks outside the class cannot see it or read its value. Why would I bother putting it in a field instead of a local variable? Well, for the same reasons that I'd do so in C#; I need to access it from other methods, namely the play method.

This command printed an array as a string:

      puts ["

Magic Number Game!
",           "------------------

",              "I'm thinking of a magic number between 0 and #{maxNum}
",             "Would you like to guess my number? (y/n) [n]:"]

It's much easier to just declare an array of strings and issue a single puts command for it to make a puts for each one.

The #{maxNum} portion is a Ruby trick known as string interpolation. The value of maxNum will be substituted for the occurrence of #{maxNum} in the string. This is roughly analogous to the String.Format() idiom in C#.

I set the value of a local variable 'playNow' to the result of the gets function, which reads a string from the input stream:

playNow = gets   if playNow.chop == "y"      play    else      puts "OK, Bye!"   end

I had to compare playNow to "y" (for yes) to make sure the user actually wants to play the game. But wait, you say, what's that .chop business? That extension will drop the last character from the value, which would be the newline character, because gets records the newline generated by the enter key when it reads the input stream. So, if the program gets a "y" it invokes the play method, otherwise it kindly says goodbye.

Normally, trying to run code from within the constructor may not be such a great idea or most objects, but for the sake of this paltry example game, it's no big deal:

def play      loop do # infinite loop!               puts "
Number?!?"         attempt = gets or break                  case attempt.to_i <=> @num            when 1 # attempt > @num               puts "Guess Lower!"               when -1 # attempt < @num               puts "Think Bigger, Mac."            else # only have == left... so...               puts "Spot on! That's it! Bye!"               break # hop out of our loop!         end      end   end

The play method is an infinite loop, meaning that it will continue to execute until the process is terminated or something inside the loop issues a break statement. I prompt the user for a number and store it in the local variable attempt.

The next bit is a little strange for a C# fan, but it actually is not all that different from a switch statement. The expression it is case-ing on (switching on) is attempt.to_i <=> @num. The first part, attempt.to_I, converts the string value attempt to an integer. (The Ruby class of objects holding small integer values is actually called Fixnum.) It's a built-in method of the %(String) class, which itself is a built-in type. The <=> operator is analogous to the C# idiom of CompareTo(). If the values are equal, <=> returns the integer 0. If the left is less than the right, an integer value of -1 is returned, and a 1 is returned if the left side of the expression is greater than the right. Basically, this is a switch for the three possible values, but instead of the C# way (switch… case), it's the Ruby way (case…when):

The very last line is the actual body of the main program:

Game.new(100)

The only thing that happens here is that an instance of the Game class is created by calling the .new method with the parameter of 100. In Ruby, .new is a special method that will invoke the initialize method, the constructor of the object. Notice the object isn't assigned. Nobody needs to see the object, so it's not stored.

The Ruby Mixin Feature
One trick I haven't covered is the ability of Ruby to change an existing class that has already been defined. This is called a mixin because it allows you to mix in your code with code that already exists. You can even create mixins that alter the built-in types in Ruby, effectively altering the way the entire language operates. To add even more variability, mixins allow you to import classes, methods, or extend classes with features of a Module (like a static class in C# 2.0) by modifying either the classes themselves or just particular instances of the classes!

You also can re-open an existing class definition and inject your own methods in, or override ones that are already there. For example, I could redefine the .to_s method of the Fixnum class, which is the class that all integers take on by default, to return something like "I, for one, welcome our robot overlords" every time. (The wisdom of doing something like this is of course questionable.) For the sake of demonstrating how very open the entire structure of Ruby is, here's how you can do this:

Fixnum.class_eval do  def to_s    "I, for one, welcome our robot overlords."  endendq = 123puts q.to_s

Of course, don't try this at home, or at least don't do it in production code.

For Further Study
The Ruby language offers many more features and goodies than I could cover in one article, but absorbing them a few at a time from here on will be no harder than what this tutorial has shown. For a quick perusal of how Ruby generally compares to C# and Java, see Table 1 below.

Language FeatureRubyC#Java
Object OrientedYesYesYes
Compiled/InterpretedInterpretedCompiledBoth (usually compiled)
Variable TypingDynamicStaticStatic
Native MixinsYesNoNo (some add-on libraries offer limited support)
ClosuresYesIn version 2.0 +No
ReflectionYesYesYes
Multi-ThreadingYesYesYes
Regular ExpressionsNativeStandard library supportStandard library support
Static and Instance MethodsYesYesYes
Type InferenceYesNoNo
Strong TypingYes*YesYes
* Ruby is still strongly typed, but the type can be inferred and easily changed at run-time. Note that this is not the same as statically typed.
Table 1. Feature Comparison Chart of Ruby, C#, and Java

One of the particularly nice things about Ruby is the voluminous mass of documentation generated by the ever-expanding Ruby user community. If you would like to learn some more about Ruby, you should check out the industry standard tongue-in-cheek guide known as Why's (Poignant) Guide to Ruby. If humor isn't your ideal way to learn a language, then you might want to see the more formal Programming Ruby, which is actually quite authoritative on the subject. No matter what your preferences are, you can get wherever you want to go with Ruby from the official Ruby Language site.

devxblackblue

About Our Editorial Process

At DevX, we’re dedicated to tech entrepreneurship. Our team closely follows industry shifts, new products, AI breakthroughs, technology trends, and funding announcements. Articles undergo thorough editing to ensure accuracy and clarity, reflecting DevX’s style and supporting entrepreneurs in the tech sphere.

See our full editorial policy.

About Our Journalist