I am the author of iOS 17 Fundamentals, Building iOS User Interfaces with SwiftUI, and eight other courses on Pluralsight.
Deepen your understanding by watching!
Analyzing Swift Protocol Extensions and C# Abstract Classes
Being a C# developer by day and a Swift developer by night has me constantly thinking about the similarities and differences between these two languages. I genuinely enjoy programming with each, and I love it when I can take a strategy that works well in one language, and see where that might cross over to the other. One of the areas I’ve been pondering as of late is the idea of how Swift and C# compare in terms of protocol extensions and abstract classes.
Swift doesn’t have the notion of abstract classes like C# does. However, it does now have an amazingly powerful feature called protocol extensions, which were explained and demonstrated in the popular WWDC15 talk on Protocol Oriented Programming. Why am I comparing protocol extensions with C# abstract classes? What are the similarities? What are the differences? Which one do I like best? The analysis of and concluding answers to these questions is the goal of this article.
Why the comparison?
What got me thinking about this? Well, I was re-watching Protocol Oriented Programming the other day and was digesting some of the arguments for why protocols often serve as better abstractions than classes. When classes are used to model a generalized abstraction, the only way they do it is through inheritance. Subclasses of some other, more generalized, base class will automatically be able to behave the same and store the same state as that base class by virtue of inheritance.
Protocols on the other hand, model abstraction through composable template-like descriptions: “Adopters of this protocol will do [x, y, and z] by implementing [function x, function y, and function z] and will have [property a, and property b]”, etc… But they define a template only – no implementations are defined within a protocol.
In C#, we have a similar mechanism to protocols called interfaces. The same paradigm of defining a template with no implementations exists in C# when we use an interface to model some abstraction. C#, of course, also has classes and can pass along behavior and state to subclasses through inheritance.
Understanding C# abstract classes
But C# has one other mechanism for abstraction: abstract classes. These special types of abstractions have the ability to behave like interfaces (or protocols) in that they can strictly define a template with no implementation, requiring all subclasses to supply that implementation. But abstract classes are unique in that they aren’t required to define a template only – they can actually provide a default implementation that may or may not be overridden in a subclass, depending on what the implementer of the subclass wants to do.
We still can’t make instances of an abstract class, just like we can’t make instances of an interface or protocol. But with abstract classes, we can provide some default implementation that can be a customization point for concrete subclasses, should the implementer of the subclass desire to override this default behavior.
Bringing it back to Swift
So… what does this have to do with Swift? Well, it seemed to me that there’s a little overlap between Swift 2.0’s new protocol extensions, and C# abstract classes. How?
With Swift protocol extensions, we can now provide default implementation for a protocol requirement, such that any adopter of the protocol automatically uses that implementation and satisfies that particular requirement of the protocol.
A given Type implementing the extended protocol could choose to provide its own implementation to customize the behavior as it needs. But if it chooses not to, it gets that default behavior for free.
Therein lies the overlap I see between Swift protocol extensions and C# abstractions. But let’s explore a little more in terms of similarities and differences between the two by analyzing an example.
Working example: Modeling athletes
Suppose for a moment that we’re working on an app that models athletic competition (such as marathons, triathlons, and other sporting events). Now, athletic competition implies athletes, does it not?
How then, could we model an Athlete
in an abstract way? That is, how can we provide just the blueprint for what an Athlete
does, so that such a Type can be entered into one of the athletic competitions and perform in it?
Modeling the Athlete abstraction with C#
In C#, we’ve got two possibilities: Create an interface, or create an abstract class.
Using an interface may look something like this:
1public interface Athlete
2{
3 public void Run();
4 public void Swim();
5 public void Cycle();
6 // Other things that an Athlete may be able to do
7}
Using an abstract class may look very similar. The primary difference is in the declaration of each method, where we mark each of them virtual
, so that they can be overridden in a subclass to provide that customization point I talked about earlier:
1public abstract class Athlete
2{
3 public abstract void Run();
4 public abstract void Swim();
5 public abstract void Cycle();
6 // Other things that an Athlete may be able to do
7}
Right off, you might be asking, “Should a marathon runner have to be able to swim and cycle??”. It’s a great question, and I’ll address it further down in the article when I discuss “refactoring for enhanced composability with Swift protocol extensions”.
Modeling the Athlete abstraction with Swift
In Swift, we essentially have one possibility that compares with C# for a pure abstraction, that is, just the blueprint describing an Athlete's
capabilities: Create a protocol.
The Athlete
protocol might look something like this:
1protocol Athlete {
2 func run()
3 func swim()
4 func cycle()
5 // Other things that an Athlete may be able to do
6}
We still don’t avoid the necessity of a marathon runner being required to be able to swim and cycle. And having read David Owens’ recent recommendations on Protocols, I’m even more uncomfortable with modeling an Athlete
this way, because it feels like we’re treating a protocol as a Type here, which he identifies as a less powerful usage of protocols.
I want to refactor this, but for this moment in time, we’ll stick with the code as-is, just to keep it as similar to the C# code as possible. I’ll discuss an option for refactoring this shortly.
Default implementation for athletic abilities
As the example stands right now in both C# and Swift, we’ve got a situation where any Type wishing to be an Athlete
, whether it be by implementing the C# interface, subclassing the C# abstract class, or adopting the Swift protocol, must provide implementations of each of those athletic abilities (run, swim, and cycle). The Type can’t not implement one of those requirements and have the code still compile. They’re requirements of what it means to be an Athlete
, so the Type must conform.
Suppose that in our scenario, any given Athlete
has one primary ability which he/she is amazing at, but when it comes to his/her non-primary abilities, the Athlete
is only able to perform at “average” skill.
This sounds like a case where it might be nice to have overridable default implementation provided. Any specific type of Athlete
could override that default implementation to perform the ability better or worse, depending on what kind of Athlete
he/she is. But if the specific Athlete
Type didn’t provide an customized override, the Type would get the “average” behavior for free.
How could we make this happen?
Default implementation with C#
In C#, abstract classes allow us to do just that. Here’s how a default implementation might be written:
1{
2 public virtual void Run()
3 {
4 // run with average speed and endurance
5 }
6
7 public virtual void Swim()
8 {
9 // swim with average speed and endurance
10 }
11
12 public virtual void Cycle()
13 {
14 // cycle with average speed and endurance
15 }
16}
So now, when we want to model a MarathonRunner
, we can override his/her ability to run, swim, and cycle as appropriate:
1{
2 public override void Run()
3 {
4 // run with average speed and __insane__ endurance
5 }
6}
It’s not terrible – At least here we can rely on the default implementation if we just want to give a MarathonRunner
“average” abilities in all areas but running.
We might prefer that a MarathonRunner
not be required to have any ability to swim or cycle, but that’s always the struggle with inheritance-based modeling. You only get to choose one base class to inherit from, and you’re bound to get some behavior that you don’t need, simply because it’s hard to model abstractions using inheritance that avoid giving you more than you need.
Default implementation with Swift
The default implementation story with Swift was non-existent until Swift 2.0 entered the scene. The approach is similar, but as we’ll see shortly, provides far more power in terms of composability. Take a look at the implementation that compares most closely with C# for now:
1extension Athlete {
2 func run() { // run with average speed and endurance
3 }
4
5 func swim() { // swim with average speed and endurance
6 }
7
8 func cycle() { // cycle with average speed and endurance
9 }
10}
Now when we want to model a MarathonRunner
in Swift, we can adopt the Athlete
protocol, and provide “override” implementations for any of the protocol’s requirements that we’d like. Anything we don’t provide a custom implementation for falls back to the protocol extension’s implementation, just like in C#:
1class MarathonRunner: Athlete
2{
3 func run() {
4 // run with average speed and __insane__ endurance
5 }
6}
Similarities
Here’s a list of the similarities I see between C# abstract classes and Swift protocol extensions:
- Both outline a set of requirements that either a subclass or a protocol adopter must implement.
- Both provide a means to automatically satisfy some (or all) of the requirements by providing a default implementation. With C#, we simply mark the method
virtual
to allow overriding in the subclass, and provide then an implementation. With Swift, we define a protocol extension that implements one or more of the protocol’s requirements. - Both ease the burden of subclasses (C#) or protocol adopters (Swift) to implement all of the requirements when reasonable default implementations could suffice.
- Both are used to provide customization points in subclasses (C#) or protocol adopters (Swift), when the default implementation is inadequate.
Differences
So there are some similarities that I hope you can see and appreciate between C# abstract classes and Swift protocol extensions. But there are some major differences that should also be recognized:
- Fundamentally, C# abstract classes are a “behavior by inheritance” tool, while Swift protocol extension are a “behavior by composition” tool.
- Consequently, C# abstract classes impose a significant limitation: subclasses can inherit from one and only one base class. Swift protocols, on the other hand, can be decomposed into fine-grained, specific requirements that can later be re-combined and composed into more robust and dynamic Type specifications. While C# interfaces provide this same composability, they don’t have the ability to provide default implementation, which is a significant difference between the Swift counterpart.
- As a consequence of that, subclasses of a C# abstract class get all of the behavior, whether they need (or want) it or not. Swift protocols, being composable, allow a Type to conform to just the pieces it needs. The protocol extension can still exist to provide default behavior when it’s appropriate. But if a certain Type needs no ability to [do some thing], it simply drops conforming to that protocol and no superfluous behavior is imposed upon the Type.
Preferring one over the other
Needless to say, I prefer Swift protocol extensions over C# abstract classes (shocker). I love the composability they offer, while at the same time allowing me to provide default implementations where it’s appropriate. In my opinion, Swift protocol extensions are the perfect blend of interface and abstract class in C#. If only C# had “interface extensions”. :]
Since we can apply multiple protocols to a Type to signify what the Type can do, and essentially compose its behavior, how might we diverge from the constraints we had previously when we tried to stick closely with the C# abstract class paradigm?
Recall that I was uncomfortable with making a MarathonRunner
have ability to swim and cycle, however “average” that ability may be. What I really want is to break things out a bit more, but still be able to provide that default implementation when I want it.
How might I refactor this by leveraging even more of the power of Swift protocol extensions?
Refactoring for enhanced composability with Swift protocol extensions
I think I might like to define 3 protocols instead of 1. Rather than modeling things as Athletes
, I’d much rather model some athletic behavior, and let the specific kinds of athletes adopt whatever behavior is most appropriate for each athlete type.
So I’ll ditch the Athlete
protocol, and define these three instead:
1protocol Runnable {
2 func run()
3}
4
5protocol Swimmable {
6 func swim()
7}
8
9protocol Cycleable {
10 func cycle()
11}
Alright… now… how about some default implementation?
1extension Runnable {
2 func run() {
3 // run with average speed and endurance
4 }
5}
6
7extension Swimmable {
8 func swim() {
9 // swim with average speed and endurance
10 }
11}
12
13extension Cycleable {
14 func cycle() {
15 // cycle with average speed and endurance
16 }
17}
Excellent. Now to cap things off, I’ll define some Types that adopt just the protocols that are needed:
1struct MarathonRunner: Runnable {
2 func run() {
3 // run with average speed and __insane__ endurance
4 }
5}
6
7struct Triathlete: Runnable, Swimmable, Cycleable {
8 func swim() {
9 // swim with lightning speed and crazy endurance
10 }
11
12 // fall back to protocol extension's average run speed & endurance
13 // fall back to protocol extension's average cycle speed and endurance
14}
15
16struct Andrew {
17 // Let's not impose the re-definition of any of the athletic terms, shall we?
18}
Notice how the different types of athletes only pick up the behavior that’s relevant to their ability. Nothing more, and nothing less.
I also enjoy being able to look at a Type declaration like Triathlete
and see that he/she is able to run, swim, and cycle. It feels right to compose abilities this way. And it’s even more convenient that some of the Triathlete's
behavior is already implemented for me by virtue of the protocol extension.
Wrapping up
I hope this analysis helped you see some of the same things I saw when it comes to how abstractions can be modeled with Swift, and how that compares with others languages like C#.