6 .NET Myths Dispelled
According to Wikipedia, .NET will be celebrating it’s 21st birthday this year on February 14, 2022.
It’s been around for so long that I think many myths and misunderstandings about .NET from the early days persist.
In celebration of .NET reaching Minimum Legal Drinking Age here in the US, let’s dispel 6 common myths about .NET!
Myth 1: .NET is for Windows
This myth persists from the early days of the .NET Framework. Indeed, it was true: the .NET Framework was initially built for Windows and in its internals, had many references to the Win32 APIs via P/Invoke which barred it from being cross platform and this persisted even while the Mono project was started by Miguel de Icaza.
It wasn’t until Microsoft got serious with .NET Core did they address many of the gaps in Mono and the lingering dependencies on the Win32 APIs.
Today, .NET 6 — the most current .NET — runs on Windows, Linux, and macOS with support for x86, x64, Arm32, and Arm64.
This means that, yes, you can build .NET applications on the latest M1 MacBooks:
And run them on the latest AWS Arm-based EC2 instances. Microsoft’s official Docker images includes builds for all major Linux platforms:
This also means that you can build .NET in your CI/CD pipelines on Linux whether you’re using GitHub, GitLab, or other CI/CD tools.
Myth 2: It’s Slower than Node/Python/Go/Rust
In reality, .NET 6 is extremely fast and in web workloads, provides many times the throughput of all frameworks running on Node and Python.
Where this myth may have started is with earlier versions of ASP.NET. You see, ASP.NET and .NET has always supported asynchronous programming models (what we know today as async/await), but it was somewhat awkward to use and inaccessible to developers in the earlier days (using async delegates) and thus rarely (and I mean very, very rarely) ever used.
In the TechEmpower Benchmarks, Round 15 from February 14, 2018, you can see that ASP.NET trails Node.js:
But by Round 20 in February 8, 2021, it is absolutely crushing Node (teal-green) and Python (blue):
What seems insane is that .NET scores 3x higher on JSON handling than Node and scores an order of magnitude higher in plaintext handling. Don’t be fooled by this chart; I’ve left only JavaScript, Python, Rust, and Go runtimes. Node is in 56th position while Express is all the ways down at the bottom in 94th.
In gRPC benchmarks, .NET is also crushing it (an order of magnitude greater throughput than Node):
If I told you that there was a way to achieve multiples of your current application throughput using a well-supported, mainstream, mature, open source, mutli-platform runtime and set of languages on your current infrastructure — wouldn’t that be worthwhile to seriously consider?
It’s not just in web benchmarks. In fact, .NET even trounces Go.
Of course, in the real world, .NET will lose to Go, Python, and Node when it comes to “cold starts” because of the nature of the VM and thus making it unsuitable for certain types of applications. However, it’s not nearly as bad as it once was. Tai Nguyen Bui has a great set of benchmarks that show how .NET cold starts can be reduced in AWS Lambda with very little effort and the Microsoft team is working on an improved native ahead-of-time compilation (“native AOT”) for .NET 7.
I have no doubt that .NET will continue to get faster.
Myth 3: It’s a Legacy Platform
To be fair, .NET would now be considered an adult here in the US so it’s easy to see it as a “legacy” platform with the new cool kids being Go and Rust.
Yet I find this assessment of the platform to be misaligned with reality. In 2010, .NET shipped with the Dynamic Language Runtime (DLR) and in doing so, ushered in an era of rapid and continued innovation with respect to the runtime and supported programming languages as it allowed dynamic languages and dynamic language features to be incorporated on top of .NET. Here’s a blog post I wrote in 2009 showing how to implement the Visitor pattern using double dispatch in C# 4.
Today, it is possible to build using a mixture of object-oriented and functional techniques in .NET. The runtime supports:
It has lambda closures, generics (which Go is just getting around to), extension methods, anonymous types, record types, local functions, and more!
With LINQ, C# looks an awful lot like JavaScript:
1 2 3 4 5 |
// TypeScript/JavaScript const names = people.map(p => p.firstName); const chens = people.filter(p => p.lastName.toLowercase() === "chen"); const smiths = people.map(p => p.firstName) .filter(n => n.toLowercase() === "smith"); |
1 2 3 4 5 |
// C# var names = people.Select(p => p.FirstName); var chens = people.Where(p => p.LastName.ToLowerInvariant() == "chen"); var smiths = people.Select(p => p.FirstName) .Where(n => n.ToLowerInvariant() == "smith"); |
It should be no surprise that C# and TypeScript bear a striking resemblance because they were both designed by Anders Hejlsberg or Microsoft:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
// TypeScript interface IRepository<T> { Save(entity: T): void; List(): T[]; } class Person { public firstName: string; public lastName: string; constructor(firstName: string, lastName: string) { this.firstName = firstName; this.lastName = lastName; } } class PersonRepository implements IRepository<Person> { public Save(instance: Person): void { // Do save here... } List = (): Person[] => []; public static Init(): void { var person = new Person("Amy", "Lee"); var repository = new PersonRepository(); repository.Save(person); } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
// C# interface IRepository<T> { void Save(T entity); T[] List(); } class Person { public string FirstName; public string LastName; public Person(string firstName, string lastName) { this.FirstName = firstName; this.LastName = lastName; } } class PersonRepository : IRepository<Person> { public void Save(Person instance) { // Do save here... } public Person[] List() => new Person[] {}; public static void Init() { var person = new Person("Amy", "Lee"); var repository = new PersonRepository(); repository.Save(person); } } |
C# — the most dominant language on .NET — continues to evolve and add features. And as Microsoft continues to invest in F#, C# will inherit many of the dynamic, functional elements of F#.
Myth 4: The Tooling is Expensive
Like many myths about .NET, this likely formed based on early tooling in Visual Studio which was indeed, quite expensive.
These days, not only does Microsoft provide a free, pretty much fully featured Community Edition of Visual Studio, there are other options to choose from as well:
These days, I do most of my C#/.NET in VS Code on a 2021 MacBook Pro M1:
And it works perfectly for me.
Myth 5: .NET Isn’t Open Source Friendly
Like many .NET myths, this one originates from the days of Steve Ballmer. Since Satya Nadella has taken the reigns, Microsoft’s entire trajectory with respect to open source has shifted.
.NET itself is governed by the .NET Foundation, the .NET compiler along with many other internals are all in GitHub public repos, and since 2015, it has been certified for Red Hat Linux.
While the usual suspects dominate GitHub’s language charts, C# pulls in at a respectable 9th place.
Myth 6: It’s for Boomer Enterprise Development
.NET is one of the most versatile platforms to build on, full stop. Very few languages are as accessible as C# while being able to build applications for virtually any use case from desktop to devices to web servers to 3D games. The Unity game engine natively supports C# and a ton of games are built on Unity including Cuphead, Hearthstone, and Rust!
With .NET 6 minimal APIs, Microsoft moves .NET closer to the realm of “simpler” language runtimes such as Go, Python, and Node.js.
Here’s a .NET 6 minimal Web API:
1 2 3 4 5 6 7 8 |
var builder = WebApplication.CreateBuilder(args); var app = builder.Build(); app.MapGet("/hello", () => { return "Hello, World!"; }); app.Run(); |
Compare that to Express (JavaScript):
1 2 3 4 5 6 7 8 |
var express = require('express') var app = express() app.get('/hello', function (req, res) { res.send('Hello, World') }) app.listen(3000) |
Or Fiber (Go):
1 2 3 4 5 6 7 8 9 10 11 12 13 |
package main import "github.com/gofiber/fiber/v2" func main() { app := fiber.New() app.Get("/hello", func(c *fiber.Ctx) error { return c.SendString("Hello, World!") }) app.Listen(":3000") } |
Or Flask (Python):
1 2 3 4 5 6 7 |
from flask import Flask app = Flask(__name__) @app.route("/hello") def hello_world(): return "Hello, World!" |
My hope is that as .NET turns 21, this post helps dispel a few of the long-standing myths about .NET which continue to be prevalent in the development community.
The reality is that .NET and C# are an extremely versatile and highly performant runtime and language to work with while providing many additional benefits for developers, teams, and enterprises; the platform and language continue to evolve and innovate.
Especially for teams considering TypeScript on Node.js web frameworks like Express or Nest, .NET and C# should definitely be evaluated given the tremendous advantage in throughput that can be achieved!