Escape from C# / Into the Swift Trenches - Part 1

In my previous post I introduced the Swift language from the perspective of a C# developer, demonstrating some of the more obvious differences between the two languages. In this series, I will be

Introduction

In my previous post I introduced the Swift language from the perspective of a C# developer, demonstrating some of the more obvious differences between the two languages. In this series, I will be digging much deeper into the language itself.

I will be covering just some of the differences (and similarities) between the two languages here. I have chosen to cover these items first since I believe they are the most obvious and important ones.

While I encourage you to read through all of the topics in this post, I realize some readers may not have interest in everything that is covered here. In that case, I would suggest you review the Contents above and navigate directly to the topic(s) relevant to you.

Namespaces

Before we dive into namespaces, it is useful to clarify some of the terminology used in both Swift and C# development. In C#, we have assemblies and namespaces. Assemblies are units of code (either an executable or library) that can expose certain objects to other assemblies that wish to use them. Namespaces are separate constructs (defined in code) to group one or more classes into logically related items.

Similarly, Swift has something called a module. Like assemblies, a module contains a unit of code that can be accessible by other modules. Unlike C#, however, Swift has no concept of a namespace. Instead, code is accessible implicitly by their module name.

A simple example would be that I have a third-party library called Widgets that contains a class that I care about, aptly named, Widget.

// C#
using Widgets
 
class Test
{
   Widget myWidget;
}
// Swift
import Widgets
 
class Test {
   var myWidget: Widget
}

Both syntaxes are quite similar when referencing modules or assemblies. There is a key difference, here, however. In the C# example, we are technically referencing a namespace. This namespace could consist of code that exists in one or more assemblies. Conversely, in Swift, this import literally refers to a module name.

The obvious advantage of having namespaces is that you organize your code independently of where it physically exists. So you can have several assemblies that represent a single “Widgets” library. This allows for greater flexibility overall.

The primary disadvantage of namespaces is that it is very easy to neglect keeping namespaces properly updated for a project (especially if you rename a project later). In addition, your namespaces can confuse other developers into thinking your code exists somewhere else.

Swift takes a simple (but not necessarily better) approach. Everything is accessible from code explicitly by its module name. The way your code is physically organized represents how you access it from code. It’s “self organizing” in that respect. Still, it does limit the flexibility to be had with more complex projects.

So you could say that Swift has implicit namespacing, whereas C# has explicit namespacing. In fact, in C# you can ignore adding a namespace to code altogether. In this case, your classes are just accessible in the “global” namespace. No “using” or namespace prefixes are required. But this is not recommended, and you can shoot yourself in the foot pretty quickly if you take this approach. With Swift, you can’t really make mistakes here, but at the cost of flexibility.

Exception Handling

To begin, it makes sense to show similar scenarios for error handling in both languages…

// C#
class TestClass
{
    static string methodThatThrows(string value)
    {
        if(value == "error")
        {
            throw new TastesGreat();
        }
    }
 
    static void doSomething()
    {
        // Propagates error up through call stack
        TestClass.methodThatThrows("error");
                 
        // Use 'do' like 'try' in C#
        try
        {
            TestClass.methodThatThrows("error");
        }
        // Catching Out of Memory only.  All other errors will propagate
        catch(OutOfMemory e) {
            Debug.WriteLine("Out of memory");
        }
        finally
        {
            Debug.WriteLine("Do this regardless of what happens in methodThatThrow");
        }
    }
}
// Swift
enum MyError : ErrorType {
    case OutOfMemory
    case TastesGreat
    case LessFilling
}
 
class TestClass {
    static func methodThatThrows(value: String) throws -> String {
        if value == "error" {
            throw MyError.TastesGreat
        }
         
        return value
    }
     
    static func doSomething() throws {
        // Compiler Error: Call can throw but is not marked with 'try'
        TestClass.methodThatThrows("error")
         
        // Propagates error up through call stack
        // Compiler error if doSomething() has no 'throws' because
        // of propagation.
        try TestClass.methodThatThrows("error")
         
        // No error propagation.  Returns 'nil' if method raises an exception
        let value = try? TestClass.methodThatThrows("error")
         
        // No error propagation.  Returns value or asserts runtime error immediately
        let value2 = try! TestClass.methodThatThrows("error")
         
        // Use 'do' like 'try' in C#
        do {
            try TestClass.methodThatThrows("error")
            defer {
                print("Do this regardless of what happens in methodThatThrow")
            }
        }
        // Catching Out of Memory only.  All other errors will propagate
        // Compiler error if doSomething() has no 'throws' because catch
        // is not exhaustive.
        catch MyError.OutOfMemory {
            print("Out of memory")
        }
    }
}

It is almost as if C# is the tiny little brother of Swift when it comes to exception handling. The flexibility of Swift’s exception handling is pretty clear from this example above. Some specific advantages of Swift exceptions include…

  • All exceptions that are thrown MUST be caught – You can override this by using “try!”, but the compiler’s default behavior forces exceptions to be dealt with. Those that are familiar with Java will recognize this quite well. Some would argue this is a limitation, but in my opinion, this really enforces good programming practices.
  • Simpler syntax for converting exceptions into values – Often you just want to return some value when an exception happens. It is very easy to do this in Swift…
return (try? TestClass.methodThatThrows("error")) == nil ? false : true

In C# it’s a bit more work…

static bool somethingElse()
{
    try
    {
        TestClass.methodThatThrows("error");
    }
    catch
    {
        return false;
    }
 
    return true;
}

  • ‘defer’ is like ‘finally’ only better – Defer can be used in any context, not just exceptions. Any code that needs to be deferred until the end of a code block can use this keyword.
  • Exceptions are enums – They implement the empty protocol ErrorType but are otherwise just enums. This makes it simple to create several different errors that can be thrown, instead of creating a class for each, like in C#.

Enums

Enums are DRASTICALLY different in Swift as compared to C#.

Enums in C# are nothing more than glorified constant data types that can be assigned integral values (other than ‘char’). They can also be assigned multiple values (with the FlagAttribute). In addition, each item in an enum must be of the same type. A more complex C# enum looks something like this…

// C#
[Flags]
enum TestEnum: byte
{
    One = 1,
    Two = 2,
    Three = 3,
    Four = 4
}

C# allows variables of type TestEnum to be assigned one or more of the specified constants above, and each constant is assigned a specific value.

Enums in Swift, however, are first class types. Enums are not confined to integral types, and in fact, each item within an enum can be a different type. Enums can also implement protocols, reference themselves and contain additional methods and properties.

// Swift
enum TestEnum {
    case One
    case Two
    case Three
    case Four
    case None
    indirect case Pair(TestEnum, TestEnum)
     
    mutating func DoSomething(value: String) {
        self = value.lowercaseString == "one" ? .One : .None
    }
}

There is much to say about enumerations, and Apple’s documentation does a nice job of representing the power of them in the Swift language.

Lazy Initialization

Lazy initialization is a common technique used, where a property is not initialized until it is first accessed. This is useful when the action required to populate the property is expensive (perhaps an expensive database hit, or a complex calculation).

A typical lazy initialization pattern might look something like this in C#…

// C#
class LazyTest
{
    private ReallyLargeObject _myObject;
 
    public ReallyLargeObject myObject
    {
        get
        {
            if(_myObject == null)
            {
                /***Time consuming process goes here ***/
                // Database reads
                // Some calculations
                // ...
                _myObject = new ReallyLargeObject();
            }
 
            return _myObject;
        }
    }
}

In the above code, we only do the slow process if _myObject has not been set yet. And once we perform the slow process, we retain that result in _myObject so it can be returned immediately on subsequent calls.

Some keen C# developers will point out that C# does have lazy initialization support through the Lazy class. However, this is purely just a helper class and doesn’t represent any sort of special feature of the language. It is designed to provide convenience functionality for lazy loading, including support for thread-safe initialization.

Swift has a built-in support for lazy initializers. In fact, “lazy” is a first class keyword in swift. If not for this keyword, a Swift implementation would look much like the C# example above. So let’s just dive right into the Swift example using the lazy keyword.

// Swift
public class LazyTest
{
    public lazy var myObject: ReallyLargeObject = {
        /***Time consuming process goes here ***/
        // Database reads
        // Some calculations
        // ...
        return ReallyLargeObject()
    }()
}

Does it get any simpler? By using the lazy keyword, we defer execution of that closure call until the object is accessed. Even better, we can even do something like this (if we only had to worry about deferring instantiation only)…

// Swift
public class LazyTest
{
    public lazy var myObject: ReallyLargeObject = ReallyLargeObject()
}

Because lazy initializers are built-in to the language, they provide a real useful performance benefit to developers while also keeping the code just as simple as it would be without lazy loading.

Multicast Delegates

Multicast delegates (also known as events in C#) are really useful. They provide a simple means of communicating changes of state to other parts of code in a way that keeps them largely decoupled from each other.

A good example of this is, let’s say you are writing an e-mail client and you have a primary AppModel class that keeps track of all the things this application does, such as composing an e-mail, quitting the application, or archiving an e-mail.

In this scenario, other independent parts of your code may want to know that you are composing an e-mail. The main window may want to be aware of this so that it disables the “quit” menu option. The toolbar may want to be aware of such an event so all options are disabled except for the “Save to Drafts” button. The point is, here, that multiple parts of the application code may need to be aware of this “compose e-mail” state, and multicast delegates are a very effective way of accomplishing this.

C# has a built-in support for multicast delegates, where you can add one or more listeners to an event by using the “+=” syntax. When the someObject instance calls that composingEmail event, each delegate assigned via the “+=” is called in succession.

// C#
someObject.composingEmail += composingEmail;
...
void appModel_composingEmail(AppModel sender, ComposingEmailEventArgs e)
{
   ...
}

The delegate is a property of someObject of a protocol called AppModelDelegate. Let’s take a quick look at what that protocol looks like…

// Swift
protocol AppModelDelegate {
   void composingEmail()
   void someOtherEvent()
}

As you may notice, the main drawback to this method is that you can only have one listener. This concept is really nothing special. You could in fact do the exact same thing in C#. The only advantage to this, possibly, is that you can assign an instance (myAppModel) to handle ALL events at once, instead of performing a “+=” for each event raised by AppModel.

So the big question is…if Swift does not have built-in support for multicast delegates, is there any alternative? The short answer is yes, but it’s not quite that simple.

There are many answers to this problem, and Swift developers have offered their own homegrown solutions to this deficiency in Swift. Most of them fit the bill in one capacity or another, but I do have a personal favorite which I believe offers the cleanest solution, while still maintaining the spirit of Swift development.

In the end, C# has the advantage here since it is easy and straight forward, without the need for a 3rd party implementation, or rolling out your own.

Classless Functions and Variables

One last little tid bit worth covering is the fact that Swift allows functions and variables to be declared outside the scope of a class. This means that with things such as utility functions and singletons, there is no need to wrap it into a static class.

Conclusion

Based on what we have seen so far, Swift, for the most part, is a “better version of C#”. It is by no means perfect, but I do feel that it addresses (in a minor way) some of the shortcomings of C#. That being said, I am still a big fan of C# and I continue to be pleased with how it is progressing with each version.

Even though we have dug much deeper into the world of Swift since part 1, we have really only scratched the surface. One of the most significant topics yet to be covered is Garbage Collection, which I will be covering in Part 2 of this series.

The JBS Quick Launch Lab

Free Qualified Assessment

Quantify what it will take to implement your next big idea!

Our assessment session will deliver tangible timelines, costs, high-level requirements, and recommend architectures that will work best. Let JBS prove to you and your team why over 24 years of experience matters.

Get Your Assessment