I recently landed a full-time Software Engineer role working with C# and .NET, and so far, it’s been fantastic! Transitioning from Ruby and Rails is a learning curve, but I’m really enjoying the deliberate, statically-typed nature of compile-time coding. Who would’ve thought?!

As you might guess, in the same way that Ruby has gems, .NET has a package manager called NuGet and provides the same tools & libraries you need for development. However, where the process starts to differ a bit from Ruby is what C# is capable of in terms of class structure and design. All of this, of course, is new to me.

I want to walk through a simple Class declaration to show the similarities between the language syntax. Also, I'm definitely planning on writing a separate post that discusses the depths of class design specific to C# too, which I'll link here eventually. Fortunately, it’s very easy to get started with C# & .NET with a powerful sandbox IDE called LINQPad compatible with both Windows & MacOS! You will also need to install .NET, LINQPad will detect .NET installs but doesn’t install it automatically.

Type Safety


I am a big fan of quantifying topics when learning and before anything, it’s important to touch on the realm of type safety since that’s the first difference we find between Ruby & C#.

In C#, type safety is enforced at compile time. This means that when you declare a variable (say, as basic as an int), the compiler checks that you only assign values of that type, catching errors before the program even runs. For example, if you try to assign a string to an int, the code won’t compile:

csharp
int number = 5; // The type 'int' is set at compile time and cannot change.
number = "hello"; // CS0029: Cannot implicitly convert type 'int' to 'string'

In contrast, Ruby checks types at runtime and while Ruby is also strongly typed (meaning you can’t seamlessly mix incompatible types) it only raises errors when the problematic code is executed. Another example is how syntax errors are handled. For instance, many syntax errors in C# are caught before compilation, preventing the program from running until you fix them, whereas in Ruby those same errors only appear when the affected code is executed, hitting ya with a runtime stack trace.

Class on Classes


I want to start with the basic class structure that we can compare between Ruby & C#. The exciting realization is that this basic structure in Ruby is the same in C# with some different semantics! Below is an example of a basic class in C# & Ruby that achieves the same goal: “Playing” and album

Basic C# Class

csharp
public class Album
{
    public string Title { get; set; }
    public string Artist { get; set; }

        public Album(string title, string artist)
        {
            Title = title;
            Artist = artist;
        }

    public void Play()
    {
        Console.WriteLine($"Now playing: {Title} by {Artist}");
    }
}

// Test
void Main()
{
    var album = new Album(title: "Grace", artist: "Jeff Buckley");
    album.Play();
}

Basic Ruby Class

ruby
class Album
  attr_accessor :title, :artist

  def initialize(title, artist)
    @title = title
    @artist = artist
  end

  def play
    puts "Now playing: #{@title} by #{@artist}"
  end
end

# Test
album = Album.new("Grace", "Jeff Buckley")
album.play

You can start to draw lines between the two and honestly, in my opinion, Ruby wins out here. There are a couple differences I’d like to specifically note!

learnin!

Property Accessors (Getters and Setters)

C#

In C#, we have properties that encapsulate data by combining a field-like syntax with the benefits of method calls. Properties are conventionally like attributes in Ruby. The syntax below defines two public properties with automatic getters and setters:

csharp
public string Title { get; set; }
public string Artist { get; set; }

These properties are public and define both the automatic getter and setter. But just like in Ruby, you can customize a bit further if needed by defining a setter as private: public string Title { get; private set; } or even define a read-only property: public bool IsReleased { get; }. You can also expand the property definition for more control with the get or set.

Ruby

Ruby takes a more concise approach with property access using the attr_accessor method

ruby
attr_accessor :title, :artist

This one line automatically creates both getter and setter methods for the specified symbols (:title and :artist). It’s simple and straightforward! You can also use attr_reader or attr_writer for read-only or write-only attributes respectively, or define custom methods for additional control.

Constructor Syntax

C#

Constructors in C# are special methods used to initialize new instances of a class just the same as in Ruby. They share the class name and may accept parameters to set up object state at creation:

csharp
public Album(string title, string artist)
{
    Title = title;
    Artist = artist;
}

This example shows a parameterized constructor for the Album class, where incoming values are assigned to the class properties. Remember that read-only property I mentioned above? This is where we would set it: IsReleased = true.

Ruby

Ruby uses the initialize method as its constructor, which is automatically invoked when a new instance is created using the .new method. Looks very similar!

ruby
def initialize(title, artist)
  @title = title
  @artist = artist
end

Here, the initialize method assigns its parameters to instance variables (denoted with the @ symbol). Again, it definitely feels more straightforward and intuitive than the C# constructor.

Entry Point

C#

Every C# application requires a clearly defined entry point, something I’m getting used to. Usually, the method where execution begins is called Main.

csharp
void Main()
{
    var album = new Album(title: "Grace", artist: "Jeff Buckley");
    album.Play();
}

Here you can see the Main method instantiates the Album class and then calls its Play method.

Ruby

Ruby does not require a formal entry point like C#. When a Ruby script is executed, the interpreter processes the file sequentially from top to bottom.

ruby
album = Album.new("Grace", "Jeff Buckley")
album.play

Whatever is called at the bottom (or last) in your file is the de facto entry point. There is no special method required to kick off the program. This is great for smaller applications or scripts where a rigid structure is unnecessary.

Wrapping up


There are obvious pros and cons to both C# & Ruby and like with any programming language or framework, you use what best suits your situation. In my current role, I’m working with some extremely sophisticated tools built entirely in C#. They require a much more explicit, verbose approach and don’t necessarily need the MVC structure you’d typically find in a web framework like Rails.

It’s really awesome to get a chance to build in a language like C# and learn about this approach. Ruby is on the back burner for now, reserved for side projects (like this blog! 🤩), but I will always appreciate the simplicity it carries that allowed me to learn OOP incredibly quickly! I definitely wouldn’t have been able to dive headfirst into C# & .NET without the strong foundation I received from learning Ruby first.