Why Tracebit is written in C#
tracebit.com
tracebit.com
With .NET, this issue is much less pronounced. While occasional adjustments are necessary, the environment is largely self-contained, minimizing the overhead required to get things running. As long as the .NET SDK is installed, running dotnet restore is usually all it takes—even when moving the codebase to an entirely new machine.
Some third-party libraries evolve more rapidly than others, but the core .NET libraries remain remarkably stable. More often than not, you can transition between major versions without needing to modify any code.
If I were starting a long-term project that required ongoing maintenance, C# would be my top choice. For quick scripting needs, I’d lean toward Python or PowerShell. In fact, PowerShell itself is another reason I appreciate .NET—it shares many concepts, and knowing .NET makes it easier to understand PowerShell. Plus, since PowerShell can directly leverage .NET libraries, it offers powerful scripting capabilities. I was thrilled when PowerShell became cross-platform, much like .NET itself.
This is because Windows comes with some form of .NET, but also other programs end up needing to install multiple versions of the .NET runtime. Eventually, you just have them on your machine. It's the equivalent to installing every version of Python and just letting the problem sort itself out.
It's not different than other tech-stacks, you're just eating the costs up front instead of running into them in a more obvious manner.
.NET (in its entirety) has lots of breaking changes that won't work from one version to another. More recently MS has been better about documenting these, especially as .NET's footprint increases inside outside of the control of MS (non-Windows environments): https://learn.microsoft.com/en-us/dotnet/core/compatibility/...
>In fact, PowerShell itself is another reason I appreciate .NET it shares many concepts
PowerShell is built on .NET (in whatever form .NET Framework and .NET/core) lol. It doesn't "share" anything, it _IS_ shell for .NET.
I do acknowledge that .NET has its share of breaking changes from time to time, but in my experience, the friction is significantly lower compared to other environments I’ve worked with.
> PowerShell is built on .NET (in whatever form .NET Framework and .NET/core) lol. It doesn't "share" anything, it _IS_ shell for .NET.
I never said it wasn’t! I was simply speaking from the perspective of the frontend experience. While PowerShell is built on .NET, my point was about how its functionality and design make it feel familiar to those already accustomed to .NET development.
Because it's literally a "shell" into the .NET runtime/framework. PowerShell _extensively_ uses reflection capabilities.
My point was they're not sharing some design philosophy or converging, PowerShell is just exposing .NET functionality directly and in an interactive manner. You in fact, can write a pretty similar tool with a few lines of C# that makes use of dynamic compilation and reflection.
Otherwise, PowerShell shares nothing (and I mean nothing) with other .NET languages (C#, F#, etc). It's history and design choices come from things like KornShell and Perl.
Where do you think all those $variables and "-ne" operators came from?
All I’m saying is that knowing .NET makes it easier to write PowerShell scripts, and I appreciate that. If that’s not your experience, that’s fine—but that doesn’t change my perspective.
It's great to system administration, having named attributes + pipelining is awesome compared to bash/CMD
It's common to find Java binaries build 20 Years+ to run on present day systems without issue.
Both languages are very impressive and benefit from the competition.
This changed around 2014-ish, as a result C# has been really stable for a decade.
Really, though, I'm very happy there is competition in the GC enterprise languages for linux. Regardless of who is "better", the competition will continue to drive progress in this area.
It works since most new C# features are just syntactic sugar which is lowered by the compiler.
Some of them require new types in the BCL (e.g. Range/Index, marker types for required/init/records), but for that you can just reference PolySharp which will generate the missing types on the fly.
Features that will not work typically also need runtime support, so default interface methods, static interfaces and the like will not work.
All of those are also part of .NET ecosystem.
Agree with the competition remark, hence why my toolbox has been .NET, Java, JavaScript ecosystems on the first drawer for decades now.
What I can say (MS hesitance aside) is that it is excellent for web applications where the ability to sustainably bear complexity of business logic is critical to business success.
I only really saw that concern in the java ecosystem, and I am very happy .NET exists, if for no other reason than to force java to get better. Many ancillary tools (source generators, xml code generation, integration of compliance metrics) that are only ever found in java are already present and even better in the .NET world.
Java: no bonus work induced by third parties to keep projects alive, keeps working for a long time. Would recommend.
FWIW, over the last couple of years the stability story has got hugely better, thanks largely to the efforts of the Haskell Foundation Stability Working Group.
You can see the breakage inventories that I've produced for the last few GHC releases, and see that the amount of breaking changes is decreasing rapidly:
Back in 2021 I ported a small C program I wrote into Rust as part of learning the language. Since I was inexperienced even though it's small and Unix specific I added about two dozen dependencies including transitively a Windows API dependency via somebody's terminal lib.
So, that's a 2018 Edition Rust program, untouched since November 2021.
The binary of course still runs just the same as ever, I've used it a few times over the intervening years - but we'll take that as read. Lets go into the source code and build it again today, how much work will it be to get Rust source code working years later with a brand new Rust toolchain and different OS?
It just works. Not like "Well, I had to run some updates and modernisations" it just works.
I've seen plenty of Java projects that don't work on anything more recent than JDK in the enterprise space (typically large Spring systems, not even Spring Boot, also DB drivers and pooling solutions seem to be quite brittle in that regard), though I will say more or less the same about old .NET codebases (before Core was a thing), old PHP, Node.js and so on.
Most of the time, it comes down to complex dependencies.
Upgrading from the classic .NET Framework to the modern .NET is usually a long process. Simple console apps, desktop apps, and libraries might be doable with an automated csproj upgrade. But Web apps require a rewrite from scratch, because the old ASP.NET is incompatible with the modern ASP.NET Core.
When .NET 10 comes out, it will be 2 characters, double the work of the previous process. Unacceptable!
/s
The NativeAOT approach is also very interesting making it possible to build native binaries similar in size and performance to languages like Rust and Go (not quite actually, but close).
It's also possible to build "single binary" tools by using <PublishSingleFile> which comes in handy when building small helper tools for the command line.
Yes, it’s possible you’ll have to deal with Enterprise-y code sometimes. But, it’s usually your team’s choice as to what is written. Also, people don’t seem to complain quite as much about it when they encounter it at a FAANG elsewhere that tries to do “good” engineering, but that’s another discussion. :)
> They’re also stodgy as hell sometimes, but I’ll take that.
Can't speak for Java, but C# has been evolving very rapidly and for the better with each iteration.I feel like the C# team really focuses on DX when evaluating language features.
Best place to get a feel for how rapidly the language evolves for the better is the version release docs for C#:
C# 13: https://learn.microsoft.com/en-us/dotnet/csharp/whats-new/cs...
C# 12: https://learn.microsoft.com/en-us/dotnet/csharp/whats-new/cs...
C# 11: https://learn.microsoft.com/en-us/dotnet/csharp/whats-new/cs...
etc.
Compared to JS, Go, Java, etc. I feel like C# evolves significantly faster and the team is faster to validate, implement, and deliver improvements to the language.
I think this is also why some folks who tried C# say 5-6 years ago have no idea how fast the language has evolved for the better.
That's not unfair at all, I think it's a fair assessment. Industrial tools should have adults at the helm. People who care about longevity and robustness foremost. About delivering quality and stability where the main value points of a language are.
My quick take: Kotlin is a joy to write in, Jetbrains tooling is pretty good (though a bit dicey lately), build systems seem a bit eh, and the whole ecosystem is a bit too locked on spring boot. I use Quarkus, which has been pretty good, but it is much smaller in terms of user base.
During the time I was working with .net from 2005-2020, I saw the transition from Webforms to ASP.NET MVC to the MVC/WebAPI split. I also saw the transition from Linq2SQL to Entity Framework.
There was also the weird time when you could run ASP.Net Core and EF Core on top of .Net Framework.
Then the entire .Net Framework to .Net Core was a major upheaval.
Another benefit that is really stands out once you start using it is that you can write anything in it. It integrates to C well and you can get absolute control over the bits in memory and on disk if you want. You can avoid the garbage collector most entirely where you think you need to. You can also operate at a high level of abstraction. It has, if anything, way too many web and GUI frameworks (even just from Microsoft).
And that last part is the rub…
The downside of C# is that it is both old and rapidly evolving. That means multiple competing frameworks. That means that the complexity of the language keeps increasing. It is not quite C++ (and WAY better designed) but it has the same problem. The number of features and the breadth of syntax is non-trivial. There is also a bit of “magic” that has crept in that allows you to skip boiler-plate with the side-effect of basically have behaviour you cannot see in the code. All this is optional.
If you write everything green field with some coding standards, it is no problem. But if you want to read other people’s code, there is a fair bit of language to learn.
Like English, C# is incredibly useful and can be used anywhere. Like English, it is super easy to pick up and use. Like English, it feels like you can use it your whole life and still not know it all.
Um. Have you tried learning English? This is definitely not the case.
If you have a lot of experience in C# 2.0, later code may be quite incomprehensible. (Yes this goes for other versions vs the latest version as well)
Whereas I can pick up a C program today and have a decent understanding of what it is doing.
Then the codebase becomes legacy a lot faster than most other environments. It will compile and keep it, but programmers want to stick new features into the codebase.
F# to the limited extent I have followed it seems more stable in terms of added features. C# has certainly adopted a lot of features from F".
My second big gripe with Net is EF ORM.
If the developers using it are not reasonably aware of how a relational database works, you can get truly horrific code. A dev team at a client I was at, managed to spike the SQL Server instance to damn near 100% for at least 30 mins.
When someone discovered the what and where was causing it.
A pure SQL statement replicating what needed to be done ran in seconds.
It seems to be the doomcycle of life in development stacks. Fresh system cuts through the accumulated cruft of the old, only to gather more and more niche cruft until it collapses on a beach of arcanary while the next wave of simplicity rises behind it.
For instance, Loom took ages to make it into Java itself, finally reaching an LTS version in Java 21, but it took until Java 24 for Loom to be able to deal well with any code hsi g synchronized {} blocks. They also went back on their previous advice ("change synchronized blocks to reentrant locks to fix compatibility") which must be fun news for the projects that did follow their earlier advice.
And while introducing modules and breaking the compile for any old Java project seems to have gone through without much trouble (throwing compiler warnings everyone ignores for several releases before breaking code, of course), the language seems to dread minor backwards incompatibilities when it comes to language level improvements like nullable types, introducing a tri-state nullability definition while also refusing to implement some stricter null checking to ensure the language feature is entirely opt-in.
Java feels like the backend is picking up features as fast as Javascript or Rust or C#, but the frontend is managed like C. A weird mix between "change is scary" and "stagnation is decline". It's the language at the forefront of high-performance dynamic programming while also having had an unimplemented "const" keyword for 25 years.
I have been part of both ecosystem for their whole lifetime.
ORMs pose the same problem regardless of the language/framework or implementation. I've seen the same issues with Java's hibernate, too, no difference there.
And to make matters worse, it seems there's no resource that fully explains all of its relevant aspects, like the Rust book does for Rust for example.
There are tutorials intended for people who can't program at all, there are dry reference documents, there are resources explaining specific features, but nothing that a reasonably competent non-C# programmer could read start-to-finish to fully grasp the language.
There's the Microsoft Docs site, but it seems to contain the same or very similar information scattered through different sections.
First result when searching "C# guide" or "C# language documentation".
Or if you want a more detailed view up front, it's https://learn.microsoft.com/en-us/dotnet/csharp/tour-of-csha..., which you'll end up inside of the menu structure somewhere by clicking any of the links in the first page.
I've put several developers at work through reading this and they've gotten up to speed in the language very quickly. It's comprehensive and, at least according to what I think is important having been doing C# for 20 years, very well organized.
Maybe you just need to git gud? IDK. I appreciate the new features in C#, every time. They solve real problems I've had.
EF is a different issue. You don't have to use EF. You can use ADO.NET still if you want. It still works completely fine. NHibernate is still a going concern. I appreciate EF because I have been able to use it to treat databases more like libraries than remote services. Being able to do that makes it so I can iterate in development much more rapidly. I do miss writing a tight SQL query, but honestly, it's not necessary for 99% of cases and the other 1% has an escape hatch that isn't hard to use. So... again, I guess just git gud?
I don't know what else to say. If you don't bother to learn the language or libraries you're using, you're going to have a bad time, regardless of which.
I did a lot of programming in .Net 1.1 and quit just after 2.0 was released. I've then spent all my time in other programming languages. Past year at work we've been starting our transition to .Net. I must say I found exactly the opposite.
All the things I knew I could still do, and I found the new stuff quite readable. Sure I had to do a couple of searches to read up on some of the changes, but overall I've been productive in .Net 8.0 from the moment we started. I have had no issue reading library code, as one does to figure out what's really going on.
Also, Visual Studio guided me to rewrite my outdated code in better form, which also helped to understand the changes.
This is entirely unlike C++ which I also used a lot before and quit just shy of C++11 getting released. These days when I read some modern C++ code I frequently have absolutely no idea of what's going on.
As for EF I don't know, as the database we still have to support doesn't support it so haven't had a chance to use it yet. If we do, we will be writing our own SELECT statements for anything beyond a trivial primary key lookup though.
YMMV.
For the complex ones we like to have full control as it almost always turns into a major ops issue if the queries are not performing well.
Though perhaps if we can catch changes of the generated queries in our test suite we could let EF generate them. Trust but verify kinda deal.
For individual developers this may be great: "I learn new things" "I found a faster way" "this abstraction allows me to do X"
But for a company with more than X developers this becomes a risk.
Obviously in a greenfield startup like the article (I'm assuming) it's maybe a bit less of an issue--at least to start? Definitely a challenge, though, especially compared to something like Go or C.
Imo ORMs are useful as a way of making common actions easy and quick but that thinking they shield you from knowing what they're doing and how SQL works can quickly cause a ton of problems.
There are a lot of EF queries that don't even need to into raw SQL to radically improve (though 100% that's the case often enough). Some `n+1`'s and `ToList`'ing million record queries into memory when you need the top 5 being examples that come to mind.
C has this same “problem” but I expect it’s less prevalent because most people still write in C99 and ignore newer C features (similar to C++). https://floooh.github.io/2019/09/27/modern-c-for-cpp-peeps.h...
I think because the toolchain is unified, people tend to adopt newer C# features faster than other languages. You aren’t waiting on your permutation of msvc/clang/gcc to update, so when a new version releases, people update and start using new stuff. That said you can also write C# still like it’s 2005, but you’d be wasting time.
C# (to steal Jon Skeet’s language) has developed in the direction of removing “ceremony” in writing code. What in C# 2 would have taken like 20 lines to create a class in a CLI program and print a value can now be done in like 2 lines without sacrificing readability, and if anything is easier to read because you’re only looking at load-bearing statements instead of lots of flaff to scaffold simple behavior.
Spend the trivial bit of time it takes to learn modern C# syntax and you'll be able read any C# fine. You'll get to use modern tooling built on a language designed along with the tools, as long as you embrace it.
On the other hand, you can spend the time it takes to learn the idiosyncrasies of every different C codebase you encounter and how they handle all the little problems differently for trivial things that would be well documented and baked into C#.
If you think this is the way to go in your organization, just do that.
But I seriously don't get why it's so impossible to just read the changelog every year. Takes literally 2 hours per year. Most of the changes are quite self-explaining anyway.
C# 2.0 was 20 years ago!
I guess everyone likes to be the underdog, but thruth is the B2B space is dominated by the duopoly of Java and .Net and shows no signs of changing.
The author is imho cosplaying as a contrarian while being solidly mainstream.
I think that it's also much more common with companies that aren't primarily tech-focused, as those companies usually have cosy relationships with Microsoft. Pure tech companies, startups and FAANGs don't seem to use it much.
This also ties into geography. In the US, far more people work for startups, FAANGs and pure tech companies than in Europe, where traditional businesses and "software houses" are far more prevalent, which means there's far more C# there.
It is the cool and modern language in today's landscape. But it is popular among the companies which use it the opposite way. Now, we shouldn't complain too much. Because this is what made .NET survive in the first place. But I wish more people looked at projects like Garnet, Ryujinx, various greenfield HPC and highload libraries in the ecosystem over yet another CRUD template. Business domain modeling is much better done in F# anyway!
Startups choose Go because no one I know is excited about working with GoF-style OO code these days. It’s easier to hire for and faster than C# for most workloads.
Yes, you can write C# in a data-oriented way, but no one does. So diving into an existing codebase means dealing with OO encumbrances, and not everyone wants that.
The compilation time is fast, and so is the startup time. Cross-platform compilation with a single command and a single binary makes life easier. On top of that, most infra tooling—Grafana, Prometheus, Kubernetes, Terraform—is written in Go, so choosing Go for the backend comes with a ton of advantages over C#. I can personally attest that this is exactly why Uber and now DoorDash have chosen Go over the alternatives.
Also, no. Go comes with certain limitations C# is completely free from. It's a stronger language for greenfield projects :)
And if you are intentionally writing bad code, Go is much more fragile when you do so. It is extremely unlikely a startup would write C# in the style you dislike. Such style is not even inherent to C# and you will find plenty of convoluted and overabstracted Go code now that there is an influx of developers in Go ecosystem moving from other languages. And a good deal of really verbose and equally hard to read code added by new features as Go tries to tackle the projects of complexity it was not designed for.
Now Go handles that by delivering a deliberately limited language. This has its pros when you have a load of hires fresh out of college.
Some wise people saw it from the beginning that the language designers had went too far with their limits, and so now things need to be bolted on the language. But I think some choices are unfortunate and cannot be repaired.
------------
If you want to go functional, want a powerful Hindley-Millner type system that is not too advanced, you should look at F#. You have the full SDK, you can still sprinkle in some OOP, have C# and C interop, you can still declare some types as mutable if needed, you have proper SUM types... the list goes on.
But in my view, no language can replace design skills. A good team has at least one senior who primarily coaches juniors wrt to design.
Not once have I ever had to interact with these technologies in a way that would benefit from my application using the same language. The language they are written in is irrelevant.
You haven't had to or refuse to? I've interacted with plenty of C# teams in my career and getting them to step outside the box to solve an actual technical problem in a reasonable way is _painful_ for this very reason.
They'd rather set up a meeting or hire more consultants and stonewall a project for over a year.
Most programmers can learn and pick up C# in like a day, but Windows-based developers seem to have a lot of trouble going the other way.
What does that prove? it proves that no single developer's anecdotal evidence can be used to validate a broad-brushed claim like "The language they are written in is irrelevant."
> ...no one I know is excited about working with GoF-style OO code these days
FYI, this is C#: var namedFunctions = new Dictionary<string, Func<int, int, int>>() {
["multiply"] = (int x, int y) => x * y,
["add"] = (int x, int y) => x + y,
["subtract"] = (int x, int y) => x - y,
["divide"] = (int x, int y) => x / y,
};
log(namedFunctions["add"](x, y));
This is also C#: var multiplyN = (int[] numbers) => numbers.Aggregate(1, (a, b) => a * b);
var addN = (int[] numbers) => numbers.Aggregate(0, (a, b) => a + b);
var subtractN = (int[] numbers) => numbers.Aggregate(0, (a, b) => a - b);
var divideN = (int[] numbers) => numbers.Aggregate(1, (a, b) => a / b);
log(multiplyN(new[]{1, 2, 3, 4}));
log(addN(new[]{1, 2, 3, 4}));
log(subtractN(new[]{1, 2, 3, 4}));
log(divideN(new[]{1, 2, 3, 4}));
As is this: // Return a tuple
var runCalcsAsTuple = (int[] values) => {
return (
multiplyN(values),
addN(values),
subtractN(values),
divideN(values)
);
};
// Destructure the tuple
var (
multiplyResult,
addResult,
subtractResult,
dividResult
) = runCalcsAsTuple(new [] {2, 3, 4, 5});
Named tuple types are also kinda interesting: using Profile = (
string Username,
(string Hn, string Mastodon) Socials
);
var profile = GetProfile();
var (hnHandle, mastodonHandle) = profile.Socials;
Profile GetProfile() => ("chrlschn", ("CharlieDigital", "@chrlschn"));
If you haven't looked at C# in a while, I'd recommend taking a look with a fresh set of eyes.Small repo with examples comparing C# to JS and TS: https://github.com/CharlieDigital/js-ts-csharp
Sigil soup!
Very useful when your workload involves a lot of steps of ETL (small throwaway functions and throwaway intermediate data types)
Which workloads? In all benchmarks I've seen and made myself C# has always been faster.
Also finely grained concurrency has especially big gap: https://hez2010.github.io/async-runtimes-benchmarks-2024/tak...
Eg: https://alexyakunin.medium.com/go-vs-c-part-1-goroutines-vs-...
I’m loving the fact that the industry is getting tired of Node in the backend and the tech debt it incurs after a few years. Rediscovering old values and getting excited about them is perfectly okay. Nobody remembers all of history—reinvention is part of the process.
Thus, anything that gets in the way of that is not worth it.
Just look at https://learn.microsoft.com/en-us/dotnet/standard/security/c... and https://learn.microsoft.com/en-us/dotnet/framework/network-p... for a sneak peek into this madness.
The second article uses the wrong link too (it's for Framework, not for .NET).
I though Microsoft did better porting dotnet to Linux. I knew they don't care about Linux GUI, but I hoped they'd at least do system libraries better.
In case someone else is reading:
- AvaloniaUI/Uno
- Algorithms are dependent on what an OS crypto provider supports (which is OpenSSL in the case of Linux, so it's an OpenSSL issue), but you can always just use bindings to an alternative and wrap it in a stream, like some do with e.g. libsodium
- IO behaves differently because each OS has different IO implementation, .NET tries to homogenize it within reason, but there are differences that cannot be hidden, big surprise?
Agree on the "Algorithms are dependent on what an OS crypto provider supports" bit.
The modern version of DotNet, "Net Core" is effectively a reboot of DotNet, with a very cross platform focus and redesigned API's based on decades of experience.
Library code you wrote in C# 10 years before .NET Core will often just compile and run. Even more than code the resides developer learning. The plumbing between ASP.NET MVC (old) and ASP.NET Core (new) was completely and radically different. Yet writing an application in it was very much the same.
Though it might be worth checking Github to find example usages of the APIs. Maybe there's even some libraries that improve the developer experience with cryptography.
Frictionless, pleasant, not thinking too much how to express things (and still managing to write them reasonably idiomatic), tends to support clean encapsulated code, quite rich environment/libraries, great tools (debuggers, profilers), safe, relatively fast, not many foot guns, zero build setup, on small project zero build times, trivial to create good functional simple UI, can get fancy dynamic with reflection when I need "magic".
Basically not many pain points that would make me rage quit and almost everything I'd want is simple to achieve.
I remember making a big spreadsheet of languages and a bunch of potential areas of use. Building desktop apps, mobile apps, games, concurrency, performance, cloud native, web apps, data pipelines, and machine learning. C# is 3 out of 4 on all of those, except maybe 4/4 on games with Unity, and being cloud native with Azure, C# gives you a bunch of stuff automatically especially around observability.
What's interesting is whereas a very large community has embraced TS, C# feels like it hasn't gotten as much love despite being so similar at a language construct level (async, exceptions, generics, etc.)
JS/TS are missing expression matching that C# gained, but there's a proposal for it for JS. C# is missing discriminated unions, but there's a proposal in process for it.
Some of the niceties over the last several versions include immutable record types, stack allocated structs (ref structs), lambdas (and a host of enhancements thereof since), async/await (of course), generic math ops, platform intrinsics, pattern matching, AOT, nullable types, robust generics, reflection, dynamic types, etc. The big thing I expect next is algebraic data types.
The language has really grown by leaps and bounds, but it doesn't ever feel overwhelming, and it has really kept its verbosity to a minimum without turning into a soup of hieroglyphics.
That being said, I have found the Android/iOS solutions to be underwhelming. I understand the iOS side a bit, but I thought Android would be better. That's not to say you can't make great applications using C# on these platforms, just that it requires more effort.
I'm also not a huge fan of ASP.Net. I've never really cared for it, and I think that stems from a project I had early in my career duct-taping together another developer's classic ASP applicat^HHH monstrosity and mucking about with IIS and FoxPro (!!). I know it's not classic ASP, but once bitten, lol. I will say that it is modern and performant, but very rigid. I'd defer to others' opinions here because I have mostly avoided it.
But in general, the tooling is great, and I don't encounter much that ties you to a Windows box anymore. I know there are some differences in platform support, but most of the rough edges were handled in the early days of .NET Core 2 & 3. Now that we're way past that (the "traditional" .NET merged with .NET Core in a combined version 5 release). Now that it's on version 9, the language features and low-level hits keep coming. I can, today, write code that is 99% of what you can get from C++ (and in some edge cases can exceed it), compiled to native code, that will run on Windows & Linux (x64 & arm64), BSD & MacOS without too much trouble.
And as a fellow HNer pointed out the other day on an unrelated thread, the native interop is painless. You can load .dll, .so, .dylib files with ease. No frankenstein FFI (ala Go or Java).
The language is safe. GC can be avoided (or controlled), and it offers raw pointers, manual memory manipulation with spans, and more low-level features through the unsafe subset of functions.
I know people like to say this started as a rip-off of Java, and there is some truth to that, but I have too much respect for Anders to believe he set out to rip off Java part and parcel. There was a method to his madness, and from my perspective, C# has always been a step ahead in programmer ergonomics (but definitely not performance). Maybe that's due to the sheer intertia of the Java ecosystem, or to Sun and then Oracle being more conservative, I don't know. Hell, it probably had more to do with Microsoft's reputation.
I value programmer ergonomics and tooling, almost above all else, and I am a happy camper. You can write C# from several different angles as you see fit, and in most cases it checks all the boxes for a large subset of projects.
Indeed, it's a completely different product nowadays.
Take a look at this! https://learn.microsoft.com/en-us/aspnet/core/fundamentals/m...
There's ASP, ASP .Net WebForms, ASP .Net MVC, ASP .Net WebApi, ASP .Net Razor Pages, ASP .Net Minimal API, ASP .Net Blazor (several forms of it, too). Some of them suck, some of them are very nice, but that's incredibly confusing for people not familiar with the stack and job seekers trying to determine how much they will hate the new job.
It's like React being called jQuery RX, Vue being called jQuery RX 2 and Svelte being called jQuery SX.
And Mac OS.
With the discontinuation of VS Code for Mac, I'm pretty concerned whether Microsoft is going to keep supporting those platforms.
I've only looked at .NET briefly, but it seems that they're not even adding new Mac APIs to the C# bindings any more, and most of the documentation mentions old Mac OS releases like Catalina.
I don't think they'll ever drop Mac support completely, as plenty of people develop ASP.NET applications on their Macbooks, but I'm very hesitant to use .NET for desktop applications where native integration is key. Especially now, when Microsoft seems to be going all-in on Catalyst.
They don't seem to care much about Xamarin either, favoring other technologies like React Native.
Wait, what? Surely you mean the version of Xamarin Studio that MS bought and called Visual Studio for Mac. I just used VS Code on a Mac yesterday.
Xamarin Studio was garbage. While I will admit it is not easy to get VS Code to play nicely with .NET (an irony that is not lost on me), it still remains possible to create a not-buggy, fully featured .NET development experience in VS Code. Neither of those qualifiers were possible in Xamarin Studio.
- The absence of free-floating functions (everything is an object, and I must use static methods on static classes).
- When “using” a namespace, the convention is to make all the symbols in the “used” namespace available directly in the current namespace. That makes harder to know where symbols are coming from when reading code. And to avoid name collisions, people tend to use longer names. The convention in Go or Rust is to prefix most symbols with the package they are coming from. But perhaps I’m missing something and need to read/write more C#.
- The syntax for doc comments with tags such as <summary> is super verbose. I don’t see the point. Docs are working great without these tags in other languages. Reminds me of my J2EE days.
- I really prefer the name before type syntax (used in all new languages such as Go, TypeScript, Go, Swift, Kotlin, Zig, etc.) to the type before name syntax (used in C#, C, C++, Java, Dart).
I assume that free-floating functions are global functions. You can achieve something similar by "global using". Put this in a file and tug it away somewhere:
"global using static YourStaticClass;"
Now you can call all the static methods on the class everywhere.
As for the using vs. naming convention, most people use the IDE and hover the mouse over the type to see its identity. Once you get proficient with the IDE, you can do it using shortcuts. However, if you really want to prefix all types with a shorthand of the package it came from, you can do it with aliases in C#.
In most languages they're bound to some scope, like package, module, etc. I'm not familiar with C#, but I assume there it would be scoped in a namespace.
Regarding imports, I guess I could do something like `using c = my.namespace.ClassWithMyStaticMethods`, but I suppose it's not idiomatic in C#.
Non idiomatic? IMO, this is completely fine. Idiomatic C# doesn't mean OO all the time.
There's basically no difference between a static class and a namespace, so you're right, it's not easier, but then this kind of dissolves your whole argument too: just put your functions in static class or two, what's the big deal? It's just like a namespace after all.
2) As I wrote in another comment, my editor supports hovering with the C# LSP, but after years of reading Python, Go, Rust, and Zig, I got used to be able to see where a name comes from by simply reading it, rather than having to hover over it.
3) My problem with the verbose doc comments is not about writing, but reading.
As you wrote, all of that is not a big deal, but just feels like a step backward.
I don't understand what you suggest instead.
> in modern IDEs just write /// before a member declaration, it will insert the whole comment block for you
The problem is not the typing, it's the reading.
The super complicated comments become readable and useful when you hover the mouse cursor over something. There are also tools that can parse those comments and create documentation.
C# does want to have interfaces though, and gravitates to the common interfaces - core services - composition/DI root architecture, with lots of projects in the solution to provide separation of concerns. I think it works very well generally for business software at least, but I hear plenty of grumbling about 'complexity' so it's not for everyone.
There is no requirement to define new interfaces except for a specific coding style in a team. It is best for most components to stay as plain and simple as possible, and for the module and component level testing to be applied with as little mocking as possible (because, most of the time, it is an anti-pattern imposed on us by what is sometimes named "London's school of testing" which is just bad practice and whoever perpetuates it should apologize).
It is a predominantly community-developed language. Microsoft contributes engineers to ensure that F# works, slowly evolves and is able to consume all the new APIs introduced in C#, while community drives the evolution of F#'s own features. You can write your own RFC, have it pass through the review, get signed off by Don Syme and then, once approved, implement it (or have it implemented by someone else). Each new release usually has multiple features driven by F# community this way.
Give it a try and if you have questions - feel free to ask them on F# discord.
Imho the bigger problem is that the C# language designer continue to not take F# into account. Structs and SUM types could be a shared MSIL story perhaps.
My last greenfield product I was able to choose the stack (except the database, which I was stuck with).
Front-end? Angular, mostly because I felt it fit best with experienced .Net developers.
Back-end API layer? C#, obviosly.
So I've been working with C# for 24 years. No regrets.
With the exception of maybe Tagged Unions, it has all of the language features I could want, and the syntax is very clean and easy to read.
Plus, many, like myself, just don’t want to write OO code, no matter what. I simply don’t want to spend my time working that way. This makes hiring difficult, especially when many young engineers who start their careers with non-OO languages instinctively shy away from Java-esque languages.
That said, I welcome language diversity in the backend and love that new startups no longer default to Node. I’d rather deal with OO than fiddle with a poorly designed language that was never meant for anything but throwaway frontend code.
OOP is not going away from mainstream.
People love Python because it’s still one of the easiest languages to start with, as it doesn’t force any particular coding style on you. Underneath, it supports a bastardized version of OO, but it doesn’t care if you just use simple types and pass them around to mostly free-floating, stateless functions. This is how startups write Python anyway.
Add a huge ecosystem of libraries and a fairly well-designed type system, and you’ll appeal to a ton of people—despite being quite sluggish. C# and Java struggle to attract this crowd, which is gradually becoming the majority as the industry ages.
You can import all methods from a static class to use them as free-floating functions.
C# started as OO so older codebases are OOP heavy.
One reason I dislike Kotlin is that, despite being fairly new, Java has infected it with OO, and most Kotlin code looks exactly like Java. The whole point of Kotlin was to escape Java’s OO baggage and appeal to a new generation of coders, but it flopped hard. I guess old habits die hard, and way more Kotlin is written by Java veterans than by the younger devs it was supposed to attract.
Scala, Clojure do support OOP just as well.
Glad to know you don't make use of methods, polymorphism and dynamic dispatch in Go, Python and Typescript.
https://smlhelp.github.io/book/docs/concepts/modules/
However they aren't as powerful, because ML modules are the way to do OOP like stuff, via what is called functors.
F# as .NET language naturally has other mechanisms for this, hence why it is a basic module concept, more in the linage of Modula-2 modules.
Go is also a OOP language, regardless of how people without CS degrees fool themselves it is not one.
Not having inheritance one OOP language doesn't not make.
By definition they are OOP languages, and anti-OOP folks could do better learning about type systems.
But I’d rather write Python, Node, Go, or Rust in a bastardized, data-oriented, faux-OO way than deal with GoF-style OO in Java, Kotlin, or C#. Sure, you can write code in a non-OO way in C#, but no one does. Whereas in Python, these days, people default to a data-oriented style.
This is just how I like to program. Plenty of folks enjoy OO thinking, and I have nothing against that. But when choosing a workplace, I’m fortunate enough to be able to ask if the codebase is OO-heavy—and politely decline if it is.
Are you aware Python basic types like numbers and functions are objects with methods and inheritance?
num=23
print(num)
print(type(num))
print(dir(num))
23
<class 'int'>
['__abs__', '__add__',
'__and__', '__bool__',
'__ceil__', '__class__',
'__delattr__', '__dir__',
'__divmod__', '__doc__',
'__eq__', '__float__',
'__floor__',
'__floordiv__',
'__format__', '__ge__',
'__getattribute__',
'__getnewargs__',
'__getstate__', '__gt__',
'__hash__', '__index__',
'__init__',
'__init_subclass__',
'__int__', '__invert__',
'__le__', '__lshift__',
'__lt__', '__mod__',
'__mul__', '__ne__',
'__neg__', '__new__',
'__or__', '__pos__',
'__pow__', '__radd__',
'__rand__', '__rdivmod__',
'__reduce__',
'__reduce_ex__',
'__repr__',
'__rfloordiv__',
'__rlshift__', '__rmod__',
'__rmul__', '__ror__',
'__round__', '__rpow__',
'__rrshift__',
'__rshift__', '__rsub__',
'__rtruediv__', '__rxor__',
'__setattr__',
'__sizeof__', '__str__',
'__sub__',
'__subclasshook__',
'__truediv__', '__trunc__',
'__xor__',
'as_integer_ratio',
'bit_count', 'bit_length',
'conjugate', 'denominator',
'from_bytes', 'imag',
'is_integer', 'numerator',
'real', 'to_bytes']
How do use them in Python without OOP?There's a good pluralsight course by Zoran Horvat called, "Writing purely functional code in C#" ( https://www.pluralsight.com/courses/writing-purely-functiona... ), where he explores exactly that.
Modern C# has added a bunch of things which means you don't need to write it in the OOP style, although if you find yourself working for others then OOP is what they'll likely expect.
Main frameworks are usually still OO based, but you can write your code on top with minimal OO style.
Big surface area in terms of syntax is not really an issue anymore as LLMs will quickly explain to you anything you don't understand about the syntax.
Modern Python in startups uses loose types and fairly stateless functions. But that’s not the case in C#. Most C# code I’ve come across is extremely Java-like, and while some people find it appealing, I prefer the Go, Rust, and Zig way of coding.
Both strategies have their place. It’s just that in the startup space, OO has fallen out of grace, and no one I know is excited about using C# to solve a novel problem. Also, CTOs always complain about how hard it is to hire for OO languages these days.
If you compare it to something like Java, yes, it has much richer syntax. But having to process some extra unfamiliar syntax is compensated by removing a lot of boilerplate once you get familiar with the features. Also, Java's simpler syntax is far overshadowed by much bigger complexity and cognitive load in other areas of the Java project, including the entire plumbing besides the code itself. Ergonomics, simplicity and comfort of a .NET Core project infrastructure is pretty much unmatched.
However, I was mostly talking about the cognitive load that comes with a large syntax surface and GoF-style OO, and how starting with something like Go is just easier.
Which level of OO is correct is context dependent.
A C codebase, and a C# codebase can have the same amount of OO in it. But while its easy-ish to write Procedural or functional C# it’s quite cumbersome to write good OO in C.
However, that doesn’t mean the language won’t fight against you. Yes, you can write data-oriented code in C# as well, but most people don’t because C# has historically been an OO language, and doing so would feel jarring to many. As a result, getting dropped into an existing C# codebase won’t be pleasant if you don’t enjoy GoF-style OO.
Go, Rust, and Zig don’t have this issue, but the latter two don’t directly compete with C#. Go does and is preferred by those who want to avoid traditional OO. That’s hard to do in an ecosystem that’s pivoting only because people are moving away from it.
But I think the strongest signal for that would rather be e.g the code being related to web/service backends of some kind. I much more rarely see that kind of thing in any other context (games, mobile, scientific, desktop, …).
I stay as far as possible from web stack stuff more than specific languages, but probably for the same reason.
I never really understood this argument. The runtime, libraries and compilers are all open source now, and all cross-platform. If MS decided to suddenly be a dick again, there is no danger of being locked in to anything.
Depends on what you do.
For backend APIs I thought I'd need Rider (I'm on macOS) but I've been happy using VSCode.
I suppose for MAUI or Windows desktop it's different though.
This is completely false.
On a Mac:
brew install --cask visual-studio-code
brew install dotnet
+ install base C# extension.That's all you need to get a language server, a debugger and the full SDK. It can rename symbols, step into implementations, apply standard refactorings, etc.
For profiling there are dotnet-trace, dotnet-metrics and various utilities to visualize the traces. You can even wire all that to pprof on Linux.
JetBrains tools are very nice but they are by no means a requirement.
Edit: Oops, I'm wrong. I saw visual-studio-code but read visual-studio.
You don't need an IDE for C# any more than you do for any other language. You can use literally any form of text editing. We use IDEs because they make the job easier.
Btw, I learned writing C# with vim back in 2008 on Mono. You don't need any IDE at all to do anything with C#, all compilation etc. can also be done by invoking the compiler or build system and editing project config files any your editor of choice.
Nonsense, VS Code, Helix, NeoVim all support C# fine, e.g.
That being said, check out Avalonia: https://avaloniaui.net/ and https://github.com/AvaloniaUI/Avalonia
Cross-platform, open source, MVVM & reactive.
A breath of fresh air.
Yes: Google Maui Cross Platform App Dev
It's less that and more things that work in C# will not work in F#, and the community doesn't have enough support for it. If you use .NET for its tooling, you lose some of that with F# and wonder if you should just use a different ML language.
Want Entity Framework on F#? Code generation is broken past .NET 6. Huge pain in the ass to fix. Using Dapper is fine, but EF is nice for more than avoiding SQL.
Want Swagger support on Giraffe? They're rethinking the whole structure just to imagine how that would work.
It's very much a separate world unless you use a lot of C# directly, and that kinda defeats the point.
LINQ + MoreLinq + Extension Methods makes it really easy and fast to make type safe internal DSLs that I wouldn't want anyone I care about to have to use but worked really well for personal power tooling. (you also _can_ definitely write more sane ones, but I didn't have to for personal stuff and it let me burn off the cleverness in my brain before working on code other people might have to work with)
So I should say I'm playing a bit fast and loose with "internal DSL" here, so that might have been a little misleading.
I'm not doing anything fancy like you could do in Scala or Ruby where there are a lot of powerful things you can do to change language syntax.
The main pieces of C# I composed to get what I'm talking are: LINQ/MoreLinq: For my scripting I was almost always automating some kind of a process against collections of things, like performing git actions against a mess of repos, performing an XML transform against a bunch of app.configs, etc.
Extension Methods: Because you can add extensions methods that only appear if the collection you're operating on is a specific _type_ of collection. So I could have an extension method with a signature like this: `public static void Kill(this IEnumerable<Process> processes)` and then I could do this: `Process.GetProcessesByName("node").Kill();` (I didn't test that code, but in principle I know it works). Kind of contrived, because there are a million ways to do that, but it let me create very terse and specific method chains that imo were pretty obvious.
The last thing, that's a lot more finicky and I didn't use as often, but very powerful are ExpressionTrees: https://learn.microsoft.com/en-us/dotnet/csharp/advanced-top...
This is what EF and a lot of other libraries use to generate queries, though you can generate whatever in principle. It's basically a first class mechanism for passing a lambda into a method and instead getting an AST representation of the lambda that you can then do whatever with. E.g., traverse the AST and generate a SQL query or whatever. (apologies if I'm explaining things you already know)
Lmk if I'm missing what you're asking. Like I said, I'm definitely being a little lazy with the DSL moniker, especially compared to something like something you'd make in JetBrains MPS or a DSL workbench, or language where you can more powerfully modify syntax, but above is generally what I meant.
Finding outdated search results from 2012 is probably the biggest impediment for the ecosystem.
Or finding out, from the project XML, whether a project is .net48 and Windows only or .net6 I'd a non-trivial task. Why?
Other than that, I agree with the article. It's a mature ecosystem where the crucial bits have first-party support.
Wow, this language allows me to focus on writing programs (algorithms) instead of fighting with the language!
9 years passed by, now 'veI worked with C, C++ and C# for money and my opinion is still the same.
I've earned way, way more money in C/C++ world, but I was 5x times happier / saner in C# world.
This tends to happen when you want to write common shared libraries that are business critical and want one implementation only or when the library exist but in another language.
If anyone wants to point me to some good C# opportunities, I’m interested. (Tracebit is looking for a founding engineer, so presumably they want someone who is already an expert at C#, and I doubt they want to sponsor the visa work needed to bring someone to the UK.)
Stuff like ReadOnlySpan and IMemoryOwner are awesome to have as built-in language concepts.
But the reason C# is one of my favourite languages to code in professionally is simply because of how easy it is to setup the environment and just get to work. Admittedly on Windows, but I've learned over the last 5 years it's a pretty simliar experience on Linux too. No messing with environment variables or paths; no header file wrangling; no macro shenanigans; no messing with virtual memory settings etc etc etc.
Yeah, I get it. Choice is nice. But when it comes to my job what's most important is that I can just get to work.
We're a windows shop at work, but because C# is portable, I get to use Linux to do my development. It's really nice
Most of the time, `sudo apt install dotnet9` (or whichever metapackage version you need) just works. And then you open up a VS Code or a Rider it's ready to go, but yeah.
As a big C# proponent, my gut tells me the JVM probably has the edge here, but it's not a clean sweep. The CLR has areas where it excels. I would also freely admit that the Java ecosystem is far larger and more mature. The NuGet ecosystem is no slouch, however.
But if I were a gambling man, I would bet that AOT compilation will become much more widespread in the .NET ecosystem while Oracle will ensure that you have to pay the piper for GraalVM to do anything interesting. Call it a hunch.
For example, in Go, the development productivity is great, but I'm not so sure about feature development velocity. There are a ton of HTTP libraries, but it's a barren wasteland when it comes to Auth solutions and you have to rely on a separate service which unnecessarily complicates the infrastructure. Need to quickly put together an application that supports enterprise OAuth? tough luck
I have to admit that I haven't done much with the interweb side. Truth be told, the few times I've used Asp.Net, I used KeyCloak as the authentication provider, and that's Java-based, lol.
That being said (and damn you for making me link to ms docs yet another time today), there is a built in provider, but my recollection is it did not support OAuth2 or JWT: https://learn.microsoft.com/en-us/aspnet/core/security/?view...
It may not suit your needs, but maybe that high-level doc can help you drill down quickly and not waste too much time on it.
I will say that I do always find it amazing how large sites can be sucessfully operated without too much fuss on the platform. Stack Overflow was notorious for being a very high traffic site that ran on a small cluster of machines and was all done in Asp.Net (might have been the old Windows framework, however).
I would like to add that I've been working with Entity Framework (EFCore) for the past year, and I've found it to provide velocity on that front. Never was a big ORM fan, but I can definitely see the use cases now.
????
That's super easy. I added support for Clerk's OAuth to our app in literally 10 minutes. It was as easy as:
> token, err := jwt.ParseString(sessionToken, jwt.WithKeySet(r.keySet), jwt.WithAudience(r.audience))
JWT is from `github.com/lestrrat-go/jwx`
I'm not sure about SAML, but at this point it's probably best to not even touch it.
What else do you need?
Where .NET shines is by providing a performance ceiling completely unmatched by neither Java nor Go nor any other GC-based language. Performance ceiling in .NET sits every so slightly below Rust and C, in often an indistinguishable way. It is a very nice experience to write systems code in. It won't give you the same safety for writing concurrent code Rust does, but it is so much more productive than dealing with C or C++, especially when it comes to portable builds and tooling. Of course, it supports only a tiny fraction of platforms compared to C. But your target is more likely to be a server or a consumer device or a comparatively beefy Raspberry PI, and .NET works really well on all of those.
How does it do this, exactly? What does .NET have that is better than Java for performant code?
CIL bytecode, besides what JVM bytecode exposes, provides much lower level access. As a result, C# and Java are languages of different categories and weight classes completely as of 2025. C# is a proper systems programming language in all the ways Java is not. JVM bytecode is also a more difficult to optimize target because all calls are virtual by default and type information is erased. OpenJDK HotSpot has to perform a lot of work just to get to the baseline where CIL starts at, where you have to explicitly make a call virtual and where type information propagates through generic type arguments. OpenJDK used to have a much more powerful compiler but .NET has closed the gap in almost every area, and in other it has surpassed it, or will surpass in the upcoming release(s). It also has a world of optimizations for structs and struct generics which are monomorphized like in Rust, something OpenJDK might only get to in the future. As I said in my previous comment, performance ceiling of .NET is at approximately the level of C/C++/Rust/Zig. Somewhat lower due to compiler limitations but the gap is small enough for it to be easily competitive.
https://benchmarksgame-team.pages.debian.net/benchmarksgame/... is a good demonstration of performance difference in optimized code in these two languages (note that on <1s execution time benchmarks the startup impact also works against both, so you could look at the comparison between C# AOT and Go to get another picture)
That would be 5 measurements out of 100?
With the tiny tiny benchmarks game programs startup and warmup and shutdown costs must effect measurements longer than 1s -- but less so.
https://benchmarksgame-team.pages.debian.net/benchmarksgame/...
Here reverse-complement and spectral-norm have >25% difference. When you look at the main comparison table for either, it might seem that C# lags behind natively compiled languages, but if you look at the NAOT numbers - it is right there next to them.
So I'm putting a disclaimer because of it.
https://benchmarksgame-team.pages.debian.net/benchmarksgame/...
Not really. You don't have the same level of control over memory in managed languages
- structs, with auto, sequential and explicit layout, with complex SROA and promotion handling
- stack-allocated buffers, unsafe fixed and inline arrays, even managed objects can have their layout configured
- monomorphized struct generics compiled the same way they do in Rust
- raw and byref pointers with pointer arithmetic
- portable and platform-specific SIMD, platform-specific intrinsics
- zero-cost interop which enables calls into malloc/free at the cost of C/C++ (you do not actually need this - there is a managed reimplementation of mimalloc which is fully competitive with the original)
- static linking with native dependencies when using nativeaot
Here's a library that does this in C in case you are wondering: https://github.com/rxi/microui.
// Please, just allocate a normal array instead, there is no benefit to this if it lives throughout the whole application lifetime
var bytes = (stackalloc byte[512 * 1024]);
// or, but, please, at least use a span instead of C nonsense of tracking offsets by hand
// it's not 1980 anymore
var ptr = stackalloc byte[512 * 1024]; // byte*
// or
[InlineArray(256 * 1024)]
struct CommandList { byte _; }
var commands = new CommandList();
// or
unsafe struct State
{
public fixed byte CommandList[256 * 1024];
}
Whichever you like the most, they do have somewhat different implications - stackalloc is C alloca while struct-based definitions have the exact same meaning as in C.To avoid running into stack space limitations, you can place a fixed buffer or any of these large arrays into a static field. It gets a fixed static offset in the memory.
C# goes much further and also provides zero-cost abstractions via struct generics with interface constraints. Or without - you can also apply pattern matching against a generic T struct and it will be zero-cost and evaluated at compilation (be it with RyuJIT or ILC (IL AOT Compiler)).
That being said, Java has recently released Project Loom, but it's poorly integrated and often causes subtle issues. It'll require a couple more years to mature.
C# doesn't have anything like this for now.
https://hez2010.github.io/async-runtimes-benchmarks-2024/tak...
This overhead is indeed real, but it is basically immaterial. If you're using that many goroutines, you are going to be CPU-bound unless it's a very special type of workload like a WebSocket dispatcher.
In return, you get _normal_ stack traces, and a normal debugging experience. Not a callback hell of async/await. Also no "colored functions" nonsense.
Coroutines make sense only for something small and trivial, like the classic tree iterator. And Go now has that: https://go.dev/blog/range-functions
Whatever language you have in mind, it isn't C#. Because all those work perfectly fine there. Please also do a cursory read of what coroutines (stackful vs stackless) in the context of concurrency are. And in Go, all functions are colored, often in ambiguous way due to poor culture of not passing the context where it matters, and goroutines panicking in dependencies that cause uncatchable application crashes. Never change, Go community.
Stackful coroutines have their pros but they are not a better choice. Rust did not and will not adopt them nor any other serious systems programming language will.
You don’t have to follow noisy style you often see - tasks compose nicely and express deferred operations incredibly well in a way that will not blow up on you in a surprising way.
There is an ongoing Async2 project which will massively reduce task state machine overhead further by only ever paying for it in the code that actually suspends rather than the code that just forwards the calls or doesn’t. It will likely land as preview in .NET 10 and as full release in .NET 11.
Hard disagree. Coroutines are absolutely useless for anything non-trivial, as debugging them becomes a total hell. They are still a callback hell, just with a lot of sugary goo slathered on it.
Coroutines also result in the "colored function" problem, that is fundamental for them.
Meanwhile, Go lightweight threads just work. Debugging is simple, and you don't have to think about spooky actions at a distance from event loops that dispatch coroutines.
You seem knowledgeable on the matter, care to share some resources that might help me grok the differences?
It's the same with F#. F# used to be ahead of C#, but now every time there's a new C# language feature it's F# that has to adjust or deal with friction in some other way. Discriminated unions are the next big thing that will require F# to put up with C#.
For bread-and-butter concurrency (make a bunch of external service calls at once, collect them up and do something) you've got async/await, but you probably knew that.
Some other things you might look into as far as distributed/parallel libraries/frameworks go: - Orleans, a framework for using an actor model in C# to build distributed systems - TPL Dataflow (Task Parallel Library), a lib for composing "blocks" to create parallel dataflow graphs
Productivity wise, the tooling for C# imo is pretty top notch, and I'd just be repeating what the article said.
Something I radically prefer about Clojure is the LISP heritage of highly interactive debugging and app dev. You can get great feedback loops with C#, but it won't be what you can do with Clojure and Calva or Cursive (is cursive still a thing? it's been awhile)
On the other hand, I personally prefer strongly typed languages, especially with C#, because of how good some of the static analysis and refactoring tooling can get in things like Rider (a JetBrains IDE for C#).
I think deployments are going to be a toss up. For my money Go is still the gold standard of "just make a binary and chuck it out there" and Clojure and C# are more normal "once you're used to it, it's completely fine, but it won't blow you away"
My only hangup is that I have had my confidence shaken by Microsoft's occasional showing of hand: with the foundation drama, the debugger nonsense, and with the weird CLI live reload removal, it seems there's still some shades of old Microsoft hanging around. I don't honestly believe they'd pull a complete bait-and-switch, and let's face it, Go is backed almost entirely by Google, a company that is at least as difficult to trust in the long run, but I wish they would, or perhaps could, do something to send a strong signal that they won't meddle with things anymore. What they have done with open sourcing .NET is highly mutually beneficial to Microsoft, and I won't lie that I think it was a greater service to us than them in some regards... but at the risk of sounding greedy here, I need to be able to trust that the people in charge are not going to pull any funny business if I'm going to invest my time, effort and possibly business into an ecosystem.
> There are some - debatable - arguments that static typing reduces bugs. [...] I think the key benefit for me is what it enables in terms of reading and maintaining code. I find that static types help me understand the intent and implementation of half-remembered or unfamiliar code much more quickly.
But, that's a large part of how it helps reduce bugs, in my opinion.
The other part is that static typing can legitimately disallow certain classes of runtime errors, but obviously that doesn't in and of itself guarantee that code is overall less buggy. In practice, though, at least as far as reducing runtime crashes, JS with TypeScript has been night-and-day better than without. Maybe static typing itself is not actually guaranteed to reduce bugs, but in practice any system that can replace bits of "Just Don't Make Mistakes" with diagnostics is going to improve reliability. The only case where I have ever questioned it was MyPy, and that's because I feel the MyPy type system is just not powerful enough to properly cover the vast majority of idiomatic Python (or it requires too much effort.) If MyPy was more sophisticated, though, there's just no doubt in my mind on that one.
All in all though I do think C# is a good choice for a productive environment to write code in. Modern .NET has a good ecosystem of tools, libraries, and frameworks, impressive language design going on in its most popular languages, and it's honestly pretty good in terms of performance and scalability.
While "right tool for the right job" is a little over-indexed on, I do think that Rust occupies a bit of a different space than C#/.NET. Rust remains king for very high performance, minimal overhead, and safe concurrency; it's got very few direct competitors. You certainly could use Rust for anything you could do in C#, but I think C# is ultimately more productive. This is not hate towards Rust, though, as I personally am the type of person that finds the value proposition of Rust very appealing and I certainly plan on investing more into Rust in the future. A better comparison for C# is Go: I think they occupy a surprisingly similar space for all of their differences. And in that realm, C# compares shockingly favorably, especially modern C# on modern .NET.
I'll add to the OSS problems of poplar libraries like identity server, ImageSharp, Moq and recently Fluent Assertions having to change license due to sustainability and got absolutely raked through the coals for the audacity of doing so. And the general belief that if Microsoft ever release a competing product in the same space your cooked even if it's not even half as good.
So not only is there the question of trusting Microsoft, my confidence is shaken by the wider ecosystem too if fairly 'staple' testing libs are struggling in their shadow.
I think a really good strategy is to try to see if your goals align here. That's where C#/MS shines compared to many other languages supported by companies. Their interest is in you using C#, because they know that drives Azure adoption (well-known main reason why they're spending money on .NET). MS as a company also has a very long term strategic interest in selling stuff where it's awesome to have a ton of developers in their environment. It's been like that forever. Say what you want about Balmer, but he was very clear on this "developers, developers, developers". It's in their core DNA. A lot of their main product lines excel due to the mass of developers around them; Office (eg SharePoint and integrations), Business Central/ERP (integrations, plugins), Windows (ecosystem of software), Azure (including tooling such as Visual Studio, VSC). Gaming/Search/Live I'm less sure about, but at least for gaming C# probably helps.
C# is probably my favorite overall language and this resonates a lot with me. I did C# Windows dev for the first five years of my career. I think I've got about four years of Go sprinkled in through the rest (and mixtures of node, Ruby, Clojure, and some others filling the gaps)
When I was doing Windows dev full time I used LINQPad for almost all of my scripting because it was so convenient, and I still haven't found a clean workflow for that with C# outside of windows, partly because of things like that. I haven't checked back in the last year or so, so it might have been sorted, but I completely get that being a red flag.
I deeply respect the very intentional and consistent design philosophy of Go--it's obvious everything is there for a reason once you know the reasons--but personally prefer C#. That might just be because it's what I sort of "grew up" on.
Which reminds me that I've been meaning to go back to Go. I haven't used it in earnest since generics were added, so it's been awhile. Something I always really preferred in C# were the ergonomics of collection transformations with LINQ + MoreLinq over for loops--which isn't to say one or the other is bad, but I prefer the functional composition style, personally. Wasn't sure if those Go idioms had changed at all with their generics implementation.
.net being cross platform is its saving grace.
For instance, productivity is listed as the top reason, but his team only found C#/dotNET to be the most productive after using it. They didn't know it would be the most productive in advance. So it wasn't a reason for choosing that platform.
Other reasons listed are, 1 Open Source, 2. Cross Platform, 3. Popularity, 4. Memory Safety, 5. Garbage Collection, 6. Stability, 7. Statically-Typed, 8. Batteries Included, 9. Tooling, and 10. Performance. I think there are plenty of languages and platforms that are as good as C#/dotNET in all of these areas, but there's nothing here to suggest any of them were even considered.
I know it's not cool, but I spend very little time on distracting stupid shit in C#. No other ecosystem does it as well as .NET. It seems like such a low bar, yet almost no one can get over it.
I sit down to program for what feels like 30 minutes in a moment of inspiration, and I look up and it's been 5 hours and I got more done than I was expecting. Every other language often has me dealing with some distraction having nothing to do with the actual problem I'm hoping to solve, and sometimes robbing me of the desire to solve the problem at all.
Other languages drive me to mess with my editor config, because it's more fun than trying to convince my environment that a business problem is more important to solve than why some dependency that was importing fine a week ago is now broken. Other languages have me trying to get AI to build me something I've built before without typing any code. C# wants me to ship something quickly that works and it wants to help me do it. It's a remarkable ecosystem, and it makes it really hard to put up with the hoops the others make me jump through. 5/5, would .NET again.
C# provides many tools to allow you to defer needing to apply patterns (which you should when possible) until they are actually necessary, such as extension methods. But some patterns can provide lots of utility and prescribed ways to approach problems that provide uniformity to large codebases.
(X) Doubt
It's probably true to say it is rarely the primary consideration in B2B backends.
They should have gone with Rust if they wanted to choose something new. C would have been respectable.
>Some fairly trivial CSS build steps using node have more dependencies than our entire C# product!
I don't know if I would really trust anything else written here. This is a security-focused product. Surely they understand that explicit, external dependencies are in fact safer than internal, implicit or "quiet" dependencies that make it hard to pinpoint attack vectors. It's really trivial to say, set up "canaries" around the former dependencies vs. the latter.
Red team 101.
Also .NET's runtime is _incredibly_ transparent, which is part of the reason why it's "reflection" capabilities are so powerful. And this can often be a great thing for developers, but I don't know about a security product that's supposed to be acting as a defense line. This is why even most obfuscation programs struggle with .NET because ultimately you can't really obfuscate much (pass some elementary things that have been obsolete since 2009 at least).
.NET programs can also "mutate" dramatically with no hope of detection except by outside sources that are constantly checking for the integrity of known programs. And I'm not talking about "compile-after-delivery" vectors either.
For example it's pretty well known in some circles that the CLR's dynamic assembly initialization and reflection capabilities allow you to even bypass AMSI. For _security-focused_ products and minimizing the blast radius on endpoints/systems, .NET (like other similar tech-stacks) really should be a no-go.
>Honestly, we’re not doing real-time/systems/embedded programming; we can afford a GC pause.
God this really hurts to read. You guys are really worried about the wrong thing(s).
> I'm kind of shocked that one of the qualifiers for choosing a tech-stack wasn't security, on a product that's specifically in the cyber-sec domain?
This is a bad take. GitHub's State of the Octoverse 2020 security report is a good read[0]. The nuget ecosystem has among the lowest package advisories, the percentage of active repos receiving Dependabot alerts, and .NET packages overall have very low numbers of direct dependencies (reducing the surface area for vulnerabilities in the supply chain).A big benefit of the large first party ecosystem and broad base class library is that there are large teams of paid, professional engineers whose job is to actually track CVEs and patch vulnerabilities in .NET and C# as well as Microsoft's first party libraries.
If anything, I'd say .NET/C# are probably one of the better choices if you plan to build in a regulated or secure context because of the large first-party libraries and active monitoring and patching of CVEs by Microsoft engineers.
[0] https://octoverse.github.com/2020/ (download the full PDFs)