The Surprising Connection Between Smalltalk, Ruby, and C#: A Tale of Extension Methods
14 Nov 2024When developers think of C#âs extension methods, they often associate them with LINQ and .NETâs modern features. But did you know that this powerful programming concept has roots dating back to the 1970s?

The Smalltalk Origins
In the 1970s, Smalltalk introduced a revolutionary concept called âopen classes.â This feature allowed developers to add methods to existing classes, even core library classes, at runtime. Hereâs what it looked like:
Object subclass: #String
instanceMethods: [
shout [
^self asUppercase, '!'
]
]
This was groundbreaking at the time - the ability to modify existing types without subclassing them. This feature would later influence many modern programming languages.
Rubyâs âMonkey Patchingâ
Fast forward to the 1990s, and Ruby embraced this concept with its own implementation of open classes:
class String
def shout
self.upcase + "!"
end
end
# Now every String instance has access to this method
puts "hello".shout # Outputs: HELLO!
This feature became so popular (and sometimes notorious) in Ruby that it earned the nickname âmonkey patching.â While powerful, it highlighted both the benefits and potential dangers of unrestricted type extension.
C#: Formalizing the Concept
In 2007, C# 3.0 introduced what we now know as âextension methods.â This implementation took the lessons learned from dynamic languages and added static typing safety:
public static class StringExtensions
{
public static string Shout(this string str)
{
return str.ToUpper() + "!";
}
}
// Usage
"hello".Shout(); // Returns: "HELLO!"
C# formalized the concept and coined the term âextension methodsâ that many modern languages now use. The implementation addressed several key challenges:
- Safety: Extensions canât override existing methods
- Scope: Extensions must be explicitly imported
- Clarity: The syntax makes it clear that itâs an extension
- Performance: Compile-time binding for better efficiency
Modern Language Implementations
CLOS (Common Lisp Object System)
;; Using generic functions
(defgeneric shout (thing))
(defmethod shout ((text string))
(concatenate 'string (string-upcase text) "!"))
;; Usage
(shout "hello") ; Returns "HELLO!"
Kotlin
// Extension function
fun String.shout() = this.uppercase() + "!"
// Extension property
val String.doubleLength: Int
get() = this.length * 2
// Extension with generics
fun <T> List<T>.secondOrNull(): T? = if (this.size >= 2) this[1] else null
// Usage
println("hello".shout()) // HELLO!
println("test".doubleLength) // 8
listOf(1,2,3).secondOrNull() // returns 2
Swift
extension String {
// Simple extension
func shout() -> String {
return self.uppercased() + "!"
}
// Computed property
var doubleLength: Int {
return self.count * 2
}
// With parameters
func repeated(times: Int) -> String {
return String(repeating: self, count: times)
}
}
// Usage
"hello".shout() // "HELLO!"
"test".doubleLength // 8
"ha".repeated(times: 3) // "hahaha"
Scala
object StringExtensions {
implicit class StringOps(val s: String) {
def shout: String = s.toUpperCase + "!"
def reverseShout: String = s.reverse.toUpperCase + "!"
// With parameters
def repeatTimes(n: Int): String = s * n
}
}
// Usage
import StringExtensions._
"hello".shout // "HELLO!"
"hello".reverseShout // "OLLEH!"
"ha".repeatTimes(3) // "hahaha"
TypeScript
declare global {
interface String {
shout(): string;
reverseShout(): string;
}
}
String.prototype.shout = function(): string {
return this.toUpperCase() + "!";
};
String.prototype.reverseShout = function(): string {
return this.split('').reverse().join('').toUpperCase() + "!";
};
// Usage
"hello".shout(); // "HELLO!"
"hello".reverseShout(); // "OLLEH!"
Rust (Using Traits)
trait StringExtension {
fn shout(&self) -> String;
fn reverse_shout(&self) -> String;
}
impl StringExtension for String {
fn shout(&self) -> String {
format!("{}!", self.to_uppercase())
}
fn reverse_shout(&self) -> String {
format!("{}!", self.chars().rev()
.collect::<String>().to_uppercase())
}
}
// Usage
let text = String::from("hello");
println!("{}", text.shout()); // "HELLO!"
println!("{}", text.reverse_shout()); // "OLLEH!"
What Makes This Interesting?
The evolution of extension methods shows how programming languages learn from each other:
- Smalltalk proved the concept was useful
- Ruby showed both the power and potential pitfalls
- C# formalized the approach with static typing
- Modern languages refined the implementation further
Key Takeaways
- Many modern programming features have roots in older languages
- Different languages can learn from each other across paradigms
- Good ideas evolve as we learn their strengths and weaknesses
- Static and dynamic languages can inspire each otherâs features