Posted on 7/21/2008 10:49:36 PM by Justin Etheredge
Click here to view the entire IronRuby via C# series
In my last post about learning Ruby via IronRuby and C# we talked about variables. We covered local variables, global variables, and both instance and static variables in Ruby. In this post we are going do delve a bit more into Ruby classes and start looking at defining methods and passing around parameters.
But first, let me start off with one quick thing so that you can start to play around with Ruby a bit on your own. And that one thing is output. In C# we would do this:
Console.WriteLine("My test value");
And in Ruby, all we do is this:
puts "My test value"
Simple, now that you can spit things out to the console, lets get on with the meat of this post.
To start it off, I am going to define a quick C# class with a constructor and a method:
public class Person
{
public string FirstName { get; set; }
public string LastName { get; set; }
public Person(string firstName, string lastName)
{
this.FirstName = firstName;
this.LastName = lastName;
}
public string FirstAndLastName()
{
return String.Format("{0} {1}", FirstName, LastName);
}
}
Here you see that we have a Person class that has two properties and initializes those properties in the constructor. Then we have a method that returns a first and last name together using String.Format. You might be surprised just how similar this looks in Ruby:
class Person
attr_accessor :first_name
attr_accessor :last_name
def initialize(first_name, last_name)
self.first_name = first_name
self.last_name = last_name
end
def first_and_last_name
"#{@first_name} #{@last_name}"
end
end
So, you can see that in Ruby we have our attribute accessors instead of the properties in C#. Then we have a specialized method called "initialize". This is essentially a Ruby constructor. It is what Ruby calls in order to initialize a new instance of a class. You don't actually have to call this method though, as you'll see in a second. Next you see that we have a method called "first_and_last_name" that returns our first and last name appended together. First notice that we don't specify a return type, this is because everything in Ruby returns a value. Either that value is nil (Ruby's null) or it is a valid value. Also notice that there is no return statement, Ruby takes any statement on a line by itself as a return. You can also see that we are using our variables directly in the string, this is a common theme in Ruby where they provide syntactic sugar for doing common tasks like this.
In order to consume these classes you would just use the "new" operator in C# like this:
var person = new Person("Ted", "Smith");
And In Ruby you would use the new method, like this:
person = Person.new("Ted", "Smith")
Another useful features in C# is the "params" keyword when passing variables to methods. Your method would look something like this:
public string Concat(params string[] items)
{
string result = "";
foreach (string item in items)
{
result += item;
}
return result;
}
In Ruby this is also available, by use of the asterisk.
def concat(*items)
result = ""
items.each do |item|
result += item
end
result
end
The Ruby method does virtually the same thing, and is arguably easier to read. In the C# version we are using a "foreach" statement and looping through each item in our items array. In Ruby the same thing happens, we are given an array of items and we use the "each" method to perform the concatenation on each item. Each may not look like a method, but it is, and this is due to the fact that parentheses are generally optional in Ruby.
So, if parentheses are optional, then we could have created our Person object up above like this?
person = Person.new "Ted", "Smith"
And the answer is yes, you could. But what do I mean about them being "generally" optional? Well, there are cases, such as when you are nesting method calls that not having parentheses would lead to ambiguous situations. Take for example this method call:
person.concat(person.first_and_last_name, "test")
Now, look at that without the parentheses:
person.concat person.first_and_last_name "test"
Yep, ambiguous. In fact, Ruby tries to give the parameter to person.first_and_last_name, which takes no arguments, and so we get an error. So, if the parentheses are optiona, are the commas optional? Nope. In fact, if we do this:
person = Person.new "Ted" "Smith"
We get an error telling us that the person initialize class expects 2 arguments and we only passed one. One? Does it pass it as an array? Nope. In Ruby, the "+" sign in string concatenation is optional! In fact, there are a few other ways to concatenate strings, but I'll just tell you to stick with the plus operator.
So, we mentioned above that "each" is just a method on the "items" array. So, if "each" is a method, then what is its parameter? Well, the entire thing from "do" to "end" is the parameter. In Ruby these are called blocks, and you can define your own methods that take them. In Ruby there are two forms of blocks, above you see on type, but we can also define another type that, as a C# developer, may look a bit more familiar to you:
items.each { |item| result += item }
See, now you can probably start to see a bit better how this translates to C#. Think of it as an action delegate, and lets define this extension method real quick:
public void Each<T>(this IEnumerable<T> list, Action<T> func)
{
foreach (T item in list)
{
func(item);
}
}
Okay, now we can use this on our array in C# like this:
items.Each(delegate(string item) { result += item; });
Hmmm, C# is maybe borrowing a bit here? In fact, we could even use the Aggregate function (also called Fold) and then the Lambda syntax would make it look even more similar. But don't let blocks confuse you with lambdas, because blocks are not lambdas. Don't fear though, because Ruby also has lambdas!
Well, I'm gonna stop there for the evening. In the next post I am going to discuss control structures in Ruby a bit (if statements, loops, etc...) and then probably get back to blocks a bit, since they are used quite heavily in Ruby.
I hope you enjoyed the post!