One thing I appreciate about C# and .NET is how well they resist codebase rot. In some other languages, I often encounter situations where the development environment needs to be completely recreated—sometimes due to an updated interpreter version or other breaking changes. If you've been away from the codebase for a while, chances are you'll need to make modifications just to get it running in a more recent environment.
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 going to sound really unfair, but compared to some of the more hip languages, the .NET/JVM ecosystems feel a bit more like they have adults at the helm who at least try to maintain backward compat. They’re also stodgy as hell sometimes, but I’ll take that. There’s a real difference in culture that manifests in both what is made and what is held up as good engineering. And that has an effect of raising the bar on what is acceptable from an ecosystem POV.
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. :)
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.
Yes, they place a strong emphasis on backward compatibility, particularly with language features. This allows new C# language features to be gradually introduced into a codebase without disruption, which is especially valuable in enterprise environments. It may feel stodgy, as you say, but they continue to add interesting features with each C# release, striking a good balance between stability and innovation.
Not sure why you’re downvoted. Happy to hear your experiences good and bad with it.
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.
Upgrades between modern .NET (Core) versions are simple and painless. It’s just a single-character change in the .csproj file, plus maybe upgrading some dependencies (but old versions will usually work).
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.
This hasn't been the case for me at all, the jumps between lts releases have burnt a lot of my time figuring out how to fix broken things. Though c# is definitely my favourite language to work in :)
What difficulties did you experience? I’ve upgraded many projects from ancient C# v2 and more modern versions. There are always some things that need to be addressed, but as long as you look at the changes from the one version to the next it’s easy enough to figure out what needs to be changed. Most modern IDEs will point out the issues in an upgrade report.
Bit hazy now, but at work every time an LTS change happens the first team to do it publishes it's notes on how it fixed various issues. Off the top of my head I remember a problem with swagger, but to be fair that's a third party lib. Problems with other 3rd party libraries with version mismatches (i.e one dependency wanting one version and another wanting a different one). It's not some major gripe, just it has never been smooth sailing! :)
What I really like is the fact that you can basically build anything in this language. From command line tools over Apps or small GUIs up to full fledged Server Backends or Webservices, even WASM... everything is possible.
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.
Yup! I recently took a pretty substantial project I wrote in 2006 in Java that was sitting in a tar file, unpacked it, and it ran just fine with a current JVM. Even better, I was able to compile it again with no problems: again, with a modern JDK. I wish I could say the same for years of projects written in Rust and Haskell: the Haskell ones are almost all bit rotten since I haven’t kept them up to date with GHC, and Rust is similar. Those two are even worse since with Java I had a rich standard library, so I didn’t have any 3rd party dependencies in my 19 year old project. The Haskell and Rust ones rely on Hackage and crates, and keeping things working for that long would require me to do bonus work beyond my actual project code. I’ve had similar but less painful problems in Python land: the library APIs are mostly stable: the biggest headache there are projects that predate python3 so there’s some work to bring them up to date. That’s at least a one time investment.
Java: no bonus work induced by third parties to keep projects alive, keeps working for a long time. Would recommend.
> I wish I could say the same for years of projects written in Rust and Haskell
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.
> the Haskell ones are almost all bit rotten since I haven’t kept them up to date with GHC
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:
Between compile times and HLS feeling fragile in VSCode, I’m pretty much done with Haskell at this point. Very nice language, just not my thing anymore.
> Yup! I recently took a pretty substantial project I wrote in 2006 in Java that was sitting in a tar file, unpacked it, and it ran just fine with a current JVM. Even better, I was able to compile it again with no problems: again, with a modern JDK.
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.
Stackage is mandatory for a “stable” Haskell env, even for simple/toy projects. It means you get predictable versions for GCH + hackage libs until you explicitly choose to upgrade.
this is definitly not true anymore, when the java libs used jaxb/xml/javaee stuff. most often because of java 9. its possible to make them running but its definitly not without issues.
I did run so many 10 years old java binaries it was NEVER without issues.
From what I gather, the only time this occurred was very early in the C#/.NET ecosystem (.NET2) when generics were added in November 2005. So it should be at 20 years this year.
Both languages are very impressive and benefit from the competition.
There were more breaking milestones, .NET to WinRT 8, WinRT 8 to UAP 8.1, UAP to UWP, UWP to WinUI 3/WinAppSDK,.NET Framework to .NET Core, Silverlight, XNA, Managed DirectX.
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.
Possible. I know little more than what I can google about .NETs history; I wasn't around for it.
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.
Possibly. I'm a relatively recent comer to the c# ecosystem (2021ish). It's been good and avoids some of the warts of java.
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.
You can opt into most of them by setting LangVersion manually to your preferred C# version.
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.
The number of times I’ve had to install an older version of Java just to compile some code is not even remotely funny to me. I really hate this idea that you can have code that just won’t compile if you are using a modern environment.
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 was excited about PowerShell at first but after doing a mid size project it, I don't like it anymore. It's such an odd language with too many quirks once you get serious. I think they would have been better off using C# syntax.
The syntax is barely the problem, the weird console rendering of objects behavior, the mixed object/text representation (since not everything buys into the objects), the breaking changes in the cross platform posh, the poor library support and requiring to load the world on importing modules... there's a lot of stuff powershell got wrong enough to avoid anything with it, and I have done plenty of serious things with it :)
I mean, there's really no justification for the Get-Help cmdlet. The -h flag has been universal for decades. If it were a replacement for 'man' it would be defensible, but not when most programs do not acknowledge -h
A few years ago an option to make self-contained builds was introduced, meaning you don't need any .NET runtime on your machine to run the self-contained app.
Yes, but in the case of Python, I feel it's a bit more nuanced. Simply having Python installed isn't enough—you also have to manage virtual environments (venv, Poetry, or whatever is popular at the moment). Personally, I find this to be more of an ongoing maintenance burden.
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.
>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?
Look, I’m not disagreeing with you here. I’m not even talking about the internal mechanics—I’m simply referring to the user experience. You're fixating on my choice of words and taking my point in a direction I never intended.
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.
> One thing I appreciate about C# and .NET is how well they resist codebase rot.
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.
C# is one of my favourite languages and .NET is an awesome stack top to bottom. The interoperability between F# (a fantastic functional) language and C# is a real bonus that was just touched on in this article. Writing a compiler for .NET is easy and fun as you can target CIL (Common Intermediate Language) either as binary or text (.NET assembly basically). Something that builds in .NET today will probably run 20 years from now but faster.
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.
One of the problems with C# is the constant expansion of the language.
Microsoft wants it to be everything for everybody, instead of sharpening
it in one direction.
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.
> If you have a lot of experience in C# 2.0, later code may be quite incomprehensible.
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.
C# 2.0 was twenty years ago - in 20 years any language that is actively maintained is going change, especially as people adopt new syntactic sugar to write more concise code. However the .net team rarely ever breaks backwards compat with new features, and the only thing in recent memory was them adding a new keyword for implicit backing property fields (and I also felt their solve was elegant).
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.
I 100% agree with the first point, but the second one isn't C#/.NET related at all.
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.
Except that EF Core is way more modern, thin and with sane defaults (more like Dapper.NET Micro ORM) than traditional EF - which is a fat, untamable beast just as Hibernate (and NHibernate was). Plus, no one forces you to use ORMs. Just use plain SQL as with any other language if that suits your needs.
.NET 's initial appeal was that it felt like Java before the architecture astronauts and the bloat with a saner GUI approach to boot. These days they not only caught up but are trying to surpass in those areas.
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.
Java has been doing great recently and I feel there is a bit of a renaissance going on. There is a very mature leadership at the helm and it shows.
With .NET, it seems that they've added features too quickly without giving it enough thought up front. If you are a small team or working on a short lived project, this might not matter. But for long lived projects by large teams, I'd rather take the more conservative approach.
Java has been playing catch-up for a while after the ecosystem stagnated on Java 8. The language seems to be very willing to incorporate new changes, but most interesting improvements seem to be stuck behind "nth preview" developments (basically useless), or the release was as badly thought through as any C# feature.
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.
C# is one of my favorite languages and I generally think the features they add are high quality and provide a lot of real value, but I also agree that the flip side of that coin is a pretty large language. I think you have to be pretty good at establishing and maintaining style standards that are a bit picky about which features you're going to use, which is a non-trivial thing to do socially in a lot of orgs.
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.
But you can still write C# 1.0 compatible code and it will run ok. Just don't look at the new features. They added quite a bit of low-level performance related features recently, like span, ref, template math, readonly struct, vectors etc, which are quite useful for the performance minded, but can be safely ignored by the CRUD crowd.
Agreed. Early on before .Net Core the language features were stable. The update to the 3.5 Framework around 2007 was a big deal at the time. Now it seems that new language features are cranked out at a much greater frequency with .Net Core. That said, C# when combined with the JetBrains Rider IDE offers the best developer experience over any other language/IDE combination I’ve worked with.
You can set an old language version for a modern C# project. The compiler will behave exactly like 10 years ago, but it outputs a modern assembly.
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.
Honestly, I think the appeal to ancient simplicity is a bad take.
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#.
> One of the problems with C# is the constant expansion of the language.
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.
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.
How can you not consider venerable Jon Skeets "C# in Depth". The fourth edition is from 2019, so not that many latest features will be in there. But the fundamentals of the language, runtime etc. are still the same.
How would you compare that to the languages the author mentioned as alternatives, JavaScript and Python? Is 2007 style JavaScript and Python codebases not legacy today? What about their frameworks from that era?
This so much. We have 7 repos and like 5 different coding styles/structures. Each developer writes C# different and uses different libraries, which creates a big maintainance overhead.
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.
All you have to do is setup a single StyleCop config and enforce it across all projects, which would be the same if you used Prettier, go fmt or anything else. I don't get the 'different libraries' part, it can be literally a case in every single language. Let people use what does the job or force them to teach what you want them to use. It sounds more like your technical management issue rather than language/ecosystem problem. Since I started working with Node/TS I miss how similar and easy to read every C# codebase was.
A comment like this comes up in every thread on C#. And I get it, you want to come to your job and go home at the end of the day and not think about programming again. But I don't know what to say to that mentality because I've never felt it myself. The exact opposite is why I've stayed away from languages like C and C++ where things evolve at a glacier's pace (though I will admit C++ has gotten relatively better in recent years, though it still has a very long way to go on the tooling front).
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 went skiing at Big Sky with bunch of Microsofties as part of Microsoft ski club back in 1999. One of them was a pretty big guy who on the first day of skiing took a bad tumble and threw out his knee. He had to stay at the chalet for most of the rest of the trip, missing out on some glorious snow. I remember asking him what he did while recuperating and he showed me what became .NET. He said it was going to be awesome and transformational, with many other nice adjectives. I didn't really get that from his demo, it was pretty rough, but yeah, he was right. I've been Csharping almost every workday since it came out as 1.0 and its an excellent framework, especially these days where it runs everywhere.
"Tracebit is a B2B SaaS Security Product. If you did a straw poll of Engineers - especially readers of a certain orange website - about the ‘best’ language for such a system, I think the common responses would include Python, TypeScript, Golang, Rust, something JVM-y, maybe Elixir"
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 C# has the highest ratio of (happy) programmers to internet discussions, at least among mainstream languages. This makes it look a lot less popular and cool for people outside the ecosystem.
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.
Which is a shame, because C# is usually better at the problems many startups try to make Go work at. It is a significantly stronger offering if you treat it as a middle ground between C++ and Rust family and Go/Java family.
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!
I can understand advocating for C# over the mess that is JavaScript, but with Go, it’s a different story.
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.
> ...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.
> 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#.
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.
>Not once have I ever had to interact with these technologies in a way that would benefit from my application using the same language.
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.
As a counterpoint, I have many times had to interact with the those technologies, and thus been able to use their source code as packages and/or learn their source code techniques which I have then used in my own Go programs.
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."
I run a monitoring startup (StackScout) where I'm rewriting the main API from PHP to C#, but the actual agent you install on your servers (so typical systems programming) is and will stay written in Go. It's ok to not have all your tech stack in a single language.
I really should up my shill game...not even in a top 10 language shills on hackernews nowadays :(
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.
All these .NET has better performance at. Go explicitly trades it off for smaller memory footprint. (Yes, even the things Go is supposed to be good at, because NativeAOT is a more "high-end" solution to the problem - more scalable binary size, and borderline absent startup overhead).
People can over abstract in almost any language. The problem is that most developers, especially beginners, are not great at finding the right balance. What you miss is experience in good design.
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.
The author is most likely too young to have experienced this trend and has now rediscovered the joy of stability. Choosing Java, C#, or even Go offers a huge advantage in terms of stability, which takes years of experience to truly appreciate.
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.
One thing I don't understand however is a complaint about getting higher quality comments than the ones you respond with. Hard data? Who cares about it, we're posting opinion pieces and vibes!
As the author points out, Batteries Included is a big reason I choose C# over and over again. Every time I have to go to NPM or Crates.io and look over five or more packages to solve some seemingly basic problem I get exhausted and frustrated that I'm not actually solving the problem I need to. C# has so much built in, and while there are third party options, the Microsoft way is almost always good enough.
FYI, You are comparing the modern version of DotNet (the first link) with the old legacy version (the second link).
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.
The impressive thing between .NET Framework (original .NET) and .NET now (rebooted as .NET Core but now dropped the “Core”) in that they largely foxed the API while leaving almost all of it intact.
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.
The first link appears to be for .NET Standard, which has a common API compatible with both Framework and Core.
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.
These limitations come from cryptographic implementations provided by specific platforms, not from .NET. Can you list specific algorithms you need that are not supported?
The second article uses the wrong link too (it's for Framework, not for .NET).
As someone with no horse in this race, I must say that I'm a little disappointed in the way Linux "compatibility" deals with platform differences. Most parts of the crypto API seem to be marked as "works on Linux if/except when" which seems strange given that porting to macOS didn't seem to impose such restrictions. In some cases, the inner workings of the underlying library works differently and you get an exception when using certain functionality on Linux at all.
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.
This is an uncharacteristically involved type of comment for "someone with no horse in this race". It is unserious and/or malicious. I can't believe we are still having to deal with the same type of conversation 9 years later.
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?
AvaloniaUI/Uno are both third party open source GUIs ... not by Microsoft. The Microsoft provided MAUI does NOT run on Linux (though it runs on ALL others ... android, ios, and even Mac). Not squinting on this omission and not ignoring it, sorry!
Agree on the "Algorithms are dependent on what an OS crypto provider supports" bit.
I share the sentiment. I haven’t used it in a while (at work use different languages and in the last few years my personal coding is only Python script/Jupyter notebook bite-sized), but anytime I hop into it, it immediately "clicks" and gives a comfortable feeling, despite changing over years.
A perfect language for medium sized relatively clean and mature personal or small team projects.
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.
Each new C# release seems to borrow yet another F# feature, to its benefit. I prefer F# too, but at least I can convince the team to use C# - F# seems to be a bit too intimidating (in its awesomeness, I assume) for most devs to learn.
Same. I think it’s because it’s easy to read and write, and its worst design warts are at least not ridiculous like they are in say JS or Go, where there are massive cons to weigh against the pros. C# just feels like it’s pretty decent across the board instead.
C# is solid. It’s not necessarily the best at anything (is it?), but its floor is like a 3 out of 4 across the board.
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.)
It's bi-directional. In recent years, C# has gained destructuring, collection initializers, tuples, and a few other niceties.
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.
I am a huge C# advocate, and with the AOT stuff since .NET 7, they are really working overtime to add features for safe low-level operations.
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.
> 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.
Indeed, it's a completely different product nowadays.
Microsoft sucks at naming. They have reinvented their web stack several times, but have stuck with the ASP (.Net) moniker.
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.
> 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.
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.
Java the language is conservative by design. They always are the last mover for language changes on purpose. They feel that it allows them to make better decisions and they don’t have to adopt bad ideas until other languages try them out.
I recently inherited a C# code base at work. I agree that C# is a powerful, productive, and mature language, but as someone who has been programming mainly in Go, Python, and a bit of Zig over the past few years, there are a few things that feel like a regression in C#:
- 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).
C# started as a language tightly aligned with C++/Java, but has since moved to be something else entirely that is highly capable.
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#.
I use VSCode with the C# LSP, but I prefer to immediately see where a name comes from by reading it, rather than hovering over it. That's why I prefer to avoid global.
Regarding imports, I guess I could do something like `using c = my.namespace.ClassWithMyStaticMethods`, but I suppose it's not idiomatic in C#.
Better is `using static my.namespace.ClassWithMyStaticMethods` that gets you exactly the consuming syntax you want, even though the methods still need to be static on that class.
> I assume that free-floating functions are global functions.
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.
Yes, I'd like to be able to define functions directly under the namespace. In C#, it seems the only way to do this in to define a static method in a class, the class being part of a namespace.
Speaking as an old C# hand:
1) I don’t think static methods/classes are that big a deal. There’s a free-floating syntax for short scripts but I don’t regard this as much of a problem. There’s also the rarely used using static syntax.
2) The common convention assumes you’re using VS or Rider and can just mouse over to find where things come from. So you’re not missing anything except possibly an IDE.
3) Yeah, it is. But again, the IDE integration is great.
4) can’t argue with you there. The C-style syntax was a mistake that’s been replicated in way too many places.
1) It's just a bit verbose. I think I just rewired my brain around a programming style that is not OOP anymore.
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.
Yeah static types as containers for functions just means the hierarchy becomes easier. In A.B.C.D() you know A.B is a namespace, C is a type and D() is a static method. With free functions you’d be unsure if this is a free method in the A.B.C namespace or not. Doing reflection over all functions would require iterating types as well as the magical unnamed ”global type”. It’s just a special case that doesn’t carry its own weight. Especially with the newer syntax where you can omit the declaration of the type in top level programs so static void Main() is a valid program.
I don't see how "attaching" functions to types is easier than attaching them directly to namespaces. I don't see either why I would need to use reflection to iterate over all the functions and methods in a namespace, but I agree that if I would need that, then yes it would require adjusting the way it works today. The problem comes from the CLR where all code and data must be part of a class. C# simply mirrors that design choice. That's why C# does not offer free-floating functions. F#, which is also relying on the CLR, solved the problem by introducing the concept of "modules", in addition to the concept of namespaces. Namespaces in F# are like namespaces in C#. All code and data must be part of a module, which is part of a namespace. Under the hood, at the CLR level, modules are implemented as static classes.
> I don't see how "attaching" functions to types is easier than attaching them directly to namespaces.
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.
The wider problem is that to do a thing you have to start thinking about the design of IDoAThing and a implementation ThingDoer before you've even worked out exactly what to do yet.
The C# tooling is very refactor-friendly. I commonly just build the class then pull out the interface later.
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.
'ThingDoer' is a naming convention from Go (I'm not a fan of the fact that we have to live with the Delphi-ism that is IMyThing but at least it's just one letter).
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).
Honestly, every point you mention in my opinion is positive.
- if you feel like using static classes and methods, you should spend more time thinking about architecture
- namespaces are just what they are
- in modern IDEs just write /// before a member declaration, it will insert the whole comment block for you
Static methods in modern C# are either extension methods or some other very rare cases like mathematical functions.
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.
Every time I futz around with another language I go back to C# for backend work. Part of it is likely due to familiarity (I’ve been using it since 2002). I wish MS would put some additional resources into F#.
Microsoft has never taken it seriously, often releasing Visual Studio with broken F# support. It's fine for something you want to tinker with (like say Elm), but not something that's easy to sell to engineering managers when starting a new project.
Ionide and Rider are other options too. But I thought the broken F# support in VS is a thing of the past now?
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.
F# is absolutely alright. It has a small but nice community and various libraries that follow similar pattern - the selection may be small but the product is usually high quality.
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.
I’m in the same boat. I worked on a project with Microsoft Consulting Services when the .Net Beta 1.0 was released in 2001. I felt very lucky to be employed at the time (post 9/11, .com bubble bust) and also getting paid to work in the latest stack. Like you I have no regrets.
I recently waded into using C# to tinker with video game development using MonoGame, and I have been so surprised by how nice of a language it is to use.
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.
C# is a nice language, but it has a huge surface area in terms of syntax. Also, Microsoft’s past behavior makes a lot of people avoid it entirely before even considering it.
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.
>C# is a nice language, but it has a huge surface area in terms of syntax.
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.
Can’t argue with this. .NET is like RoR or Django, just more tightly integrated. The dev experience is better than Node with its thousand dependencies or even Python at times—with its better type system but a zillion ways to manage dependencies.
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.
Java nowadays basically means Spring Boot. Which is just best practices anyway.
And every Spring Boot project looks the same. It's boring and it works.
So from a technological standpoint, it doesn't really matter which one you choose. .NET might have an advantage here and there, but for my taste they are trying a bit too hard to sell me their cloud stuff.
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.
This leaves very few mainstream languages left like C available, as everything else does support some form of OOP, regardless of the shape it is exposed on the programming language type system.
Most modern codebases in Node and Python are devoid of OO—at least in the ones I’ve worked on over the last half-decade. Go is mainstream now too, and it doesn’t even allow writing OO code.
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.
This sounds like the pedantry FP folks are infamous for. By definition, Rust is also OO.
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.
I’ve learned programming with OOP in Turbo Pascal, C++ and Java. Switching to a different programming style, more data-oriented, using free-floating functions, in languages such as Python, Go, or more recently Zig, has been liberating. I have inherited a C# code base and I have to say that I’m not super excited about going back to using classes and objects everywhere. It’s really hard for me now to “unsee” the problem with that unnecessary complexity.
Quite relatable. Writing a lot of Go and a bit of Rust has changed how I write Python and TypeScript. Most newcomers in startups write Python in a data-oriented manner, but that’s not the case even in new Java or C# codebases I’ve seen.
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.
I'm wondering if the solution to my problem could be to start using F#, which seems to encourage a data-oriented style over an object-oriented style, while staying in the same C# and .Net ecosystem. F# has a concept of modules, holding all executable code and data. They elegantly replace C# static classes, and are mapped to static classes in the CLR.
I still like C# more than Java and know that the language now allows you to break out of GoF-style OO. But the issue is, few people actually do, and those who want to aren’t writing C#.
> Also, Microsoft’s past behavior makes a lot of people avoid it entirely before even considering it.
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.
The correct amount of OO in any language is ”as little as possible”. It’s quite possible to do this well in C#. There are levels of OO to not just on or off. E.g there is a difference between order.Process() and OrderProcessor.Process(order) and GetOrderProcessor().Process(order)
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.
It’s possible to write good code in JavaScript too; VSCode is a great example of this.
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.
It’s obviously more likely to find a C# codebase riddled with obsolete OO patterns than a Rust one.
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.
This is true in theory. However, Python also supports a wishy-washy version of OO, and most people don’t write code in an object-oriented style.
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.
The tooling argument is contradicting itself: You need a big fat IDE to work with c#. Profiling is not even part of the default tooling, Microsoft tutorials want you to upload profiles to external flamegraph tools etc.
That is just a false statement. I use VSCode on MacOS for my ASP.NET WebAPI development, including EFcore and SQLite and PostgreSQL. I deploy to Linux and never had any issues. No "fat" IDE needed at all.
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.
That "big fat IDE" is a tool, optional one, and is so fucking useful that I wish other programming languages had something at least 80% good as VS for C# is
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.
I used to work on a game where one of the mechanics was that you could write your own C# scripts. In a text box. We compiled the code in the game engine with Roslyn.
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.
C# really benefited from the trail that Java broke, and has stumbled in similar areas, notably the UI space. I know people who love Blazor but it's not for me. MAUI just looks like MSFT's latest cover fire & delay strategy; not going to get me again. But if you're writing primarily backend code in modern .Net C# is very productive, refreshingly lightweight, pretty much complete and has great tooling and lots of taleneted developers in the ecosystem.
I was pleasantly surprised to see FreeBSD part of the list of available cross-platforms running .NET. I already had a FreeBSD instance for ZFS and was able to also host a website and some workloads written in C#, avoiding the need for a separate Linux instance.
My only experience of C#/.Net programming is by way of powershell. But the comments here make me think it would be a good idea to invest some time in learning c# beyond what it can provide over add-type in powershell. I've mostly avoided it because I thought it was windows only, I've learned dart and flutter for cross platform programming. Can c# run on mobiles?
Yes. MonoGame (written in C#) for example supports iOS, iPadOS, Android, macOS, Linux, PlayStation 4, PlayStation 5, PlayStation Vita, Xbox One, Xbox Series X/S and Nintendo Switch.
This article is very useful because the author shares his thought process as why he picked C# over languages he is familiar with (such as Python), or something trendy as Rust. I wish more articles like this one would pop up every now and then here and elsewhere.
Tangentially related: When I was doing C# on Windows full time I ended up using LINQPad a ton for almost all of my daily scripting.
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)
Can you say more about how you made a DSL this way? I love C# but one of my (few) issues with it is that its “support” for DSLs is generally subpar compared to the JVM and how Kotlin and Java files can exist side by side in the same project. Would love to know tips about how you approach this!
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.
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.
If Microsoft released a new version and called it something other than asp.net, .net, .net framework, .net core or anything like that, I'd be so happy.
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.
Well… the moment you have to do FFI in Java is when you give up. It’s straight up the worst I’ve seen from all higher level languages. I rather do CGO than use JNI. Its actually less bad when using Rust but still bad.
Interesting, from 3 different jobs, all of them needed FFI. The first one was using C#. The second was Swift and Java (android). The current one is Java, NodeJS, C#, Python, Go.
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.
C# is basically Microsoft Java. So you get Java's enterprise-friendly OOP and powerful Microsoft tooling and management, things most people aren't going to want to do manually.
I have never had a chance to use C# professionally, but it was one of the first languages I taught myself when I was first learning programming when I was a kid. I have a lot of fond memories of it, and I hear so many positive things about C# and .NET Core these days… but I just don’t see very many interesting tech jobs where I would have the chance to use C#. So, I’ve mostly used Go, Rust, and TypeScript through my career up to this point.
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.)
In my opinion, one of the most underrated things by people programming in C# is the fact that they also have F# at their fingertips. They ignore it because they haven't dedicated enough time to appreciate how powerful and fantastic it is.
> They ignore it because they haven't dedicated enough time to appreciate how powerful and fantastic it is
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.
TPL seems fine in documentation but I found it very hard to be used in real world scenario. Wiring evrything to single input-output or using dozens Join blocks to achieve data structure you want for some blocks was not pleasant.
Also finishing pipeline in the right time is also not that easy. Either blocks were not finishing at all or finishing too early.
I end up writing my own flow library that is much easier to use for simpler pipelines, yes not so versatile and does not allow spanning multiple inputs across multiple blocks, but at least it does what it supposed too.
I didn't follow it close, but had an impression that C# and .Net still have a significant Windows ecosystem bias. But this article, and comments from happy C# users, may just convince me to re-learn modern C# and start using it in everyday work. I'm kinda tired of C++/Python ecosystem, and have fond memories of working with C# around 2.0-3.0.
Nice! C# is also a secret systems programming language in a way that competing GC-based languages are not. It’s actually very nice for high load environments. With the caveat that, much like in Rust, you have to actively vet your dependencies and sometimes write your own optimal implementations, which C# enables incredibly well with precise control over memory management, first-class structs, zero-cost abstractions, SIMD and a compiler that is slowly closing the gap with LLVM.
Agree. We're using C# in the backend for an MMO, and while I would still like _more_ control over the memory management, there's a ton of options for dropping down to a low level and avoiding the GC.
Stuff like ReadOnlySpan and IMemoryOwner are awesome to have as built-in language concepts.
I have used a myriad of programming languages in production in my nearly 25 years of professional programming. There are things I love (almost never "hate") about most languages.
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.
The C# Linux experience is spending five minutes googling how to install the dotnet SDK on your distro because they're all different. Then you install the IDE.
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.
They are very close. The biggest issue with Kotlin is that it has to keep up with Java language changes. It used to be a "strictly better Java", promising zero-friction migrations, but whenever Java adds a new language feature Kotlin has to accommodate it or it will no longer be able to "inherit all of the JVM ecosystem".
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#.
There's a lot of banter as to which JIT environment is more performant.
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.
OpenJDK used to offer a better baseline performance, particularly at very abstraction-heavy enterprise code. .NET has closed the gap with it since then. Nowadays, .NET can also have quite a Go-like memory profile, especially if you use NativeAOT, but has much higher GC throughput which offers a more foolproof set of tradeoffs. Go has a nice specialized GC design but you cannot tune it at all, and it is centered around moderate allocation rates and consistent latency with small memory footprint. The moment you step outside its boundaries it has problematic regressions .NET's GC implementation is robust against. Java GC implementations are even stronger, but Java also has much higher allocation traffic which offsets some of them.
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.
> 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)
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.
You've never actually verified this claim, have you? Next time - please do it.
- 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
In C, I can just write the whole program without calling malloc. I write my own allocator tuned for my programs behavior, I even have multiple allocators if needed. I just have a lot more control available. If you can't do this in a language, you can't claim its performance is close to C. Performance is about control.
You keep ignoring the point. Show me how can I write a program in C# that works with a fixed size memory region, lets say 512KB. It should not do any dynamic memory allocation.
// 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)).
Thanks for the response. I mean from a productivity point of view when building real-world applications.
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
Asp.Net supports both internal & external auth(n|z) providers.
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.
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.
I still prefer Go, for its first-class lightweight threads. They make _everything_ so much better to debug, compared to async/await that is available in C# and Kotlin.
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.
Go is worse at _lightweight_ concurrency because each Goroutine is much costlier than each .NET task. Also Go has less pleasant concurrency semantics comparatively speaking because Goroutines cannot yield a value - you have to spawn a channel on top of spawning a Goroutine or you need to use a wait group or similar. .NET offers better experience for concurrency-centric code at a much lower overhead for code that has light of finely grained concurrent operations.
A million tasks results in 2.5G memory spent (for stacks).
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
> In return, you get _normal_ stack traces, and a normal debugging experience.
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.
IIRC C# investigated virtual/green threads, but decided to drop it because of poor interactions with reentrant code from async/await. The fact that C# went all in on async/await when it was the rage has prevented it from adopting a better solution. Java's conservative attitude fares better for such fundamental changes.
There is reason async/await got so popular after it was adopted by C# based in the early Async work in F# :)
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.
> Stackful coroutines have their pros but they are not a better choice.
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.
Haven't written C# for 8 years, but Visual Studio used to be dog slow. But that's the only criticism I can think of, love the language. I remember when ReactiveX came out, C# became close to unbeatable at that point.
How does C# compare to Clojure, at which I am looking lately a lot with a wish to use it for distributed applications productivity-wise, concurrency-wise and productivity-wise, including deployments in production?
C# and Clojure are probably my two favorite languages, but I've done much much less Clojure in production, and for my personal disposition C# is my overall favorite.
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"
I am a certified Go zealot; pretty much anyone who's trolled through enough threads here can attest to that. However, I am increasingly interested in .NET these days. I think Microsoft and the .NET community have built something truly compelling, and the language design of C# is superb (and I'm not saying it's perfect by any means, but I'm routinely impressed with what they accomplish version over version.)
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.
> My only hangup is that I have had my confidence shaken by Microsoft's occasional showing of hand...
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.
> My only hangup is that I have had my confidence shaken by Microsoft's occasional showing of hand...
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.
> the debugger nonsense, and with the weird CLI live reload removal
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.
Honestly, this reads more like someone who wanted to use C# from the outset and needed to come up with reasons to justify it, rather than an honest comparison of a number of languages and platforms with a view to selecting the best fit for the problem domain. None of the listed reasons for choosing C# are unique to C# or its ecosystem. And some of them seem to have been discovered to be applicable only after the fact.
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'd have written this exact comment two years ago. After two years using C# and .NET in anger, I'd pick it for almost any project. It's better than Python, Ruby, Go, Elixir, Typescript, Etc. and better than Rust unless you absolutely must squeeze every ounce of performance out of something, and even then, sometimes C# is better.
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.
All that said, you .NET folks need to let your patterns die in a fire. Modern C# doesn't require any of this crap anymore. Use functions. They're great. The world of nouns is behind us.
Honestly, I could see myself writing this some time ago, but I somewhat disagree with it now. The problem isn't the patterns themselves, the problem is the overuse.
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.
F, but yes. I think it often isn't the primary reason (look at how much crap is written in Python or Ruby) but I wouldn't say "rarely" or we wouldn't have so much code written in C/C++.
It's probably true to say it is rarely the primary consideration in B2B backends.
i love dotnet / c# and i would love to work with it again. i have been in the typescript/node world for the past 10 or so years, and while productive, it leaves much to be desired.
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?
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.
> How is being focused on security for a security product a bad take?
They're saying C# is more secure than other languages.
> Why are you following me around between threads?
You've accused a few users of this. It's a small site. I Think we're just noticing when there's an oddly hostile take on C# that's light on details, it's you.
I don't use C#, so I'm curious about your perspective. I'm still waiting for substantive answers to your thoughts on things (like https://news.ycombinator.com/item?id=42873130) and not just mud slinging. You may have valid complaints, but it's coming off like you're spreading FUD. This comment isn't much better because they the original poster made their thoughts clear and you said "You think security is bad?!"
The article mentioned nothing about stack security. I commented on it. I gave a pretty CLEAR and EXACT example on the type of exploits that the .NET runtime allows.
I didn't mention C# one time in my post except for what was quoted in the article. It was about the .NET runtime. That's hardly "hostile" to C# when it's a technical fact and has been demonstrated and used out in the wild.
And then the same people that spend all of their time in a religious war about C# posted something irrelevant to what was stated, and strangely didn't post ANYTHING on the article before I did. Only after I commented they show up, responding directly to me.
And here you are again, being a weirdo on the internet. Seriously go do something else. C# doesn't love you, it's a tool. Not a religion.
EDIT: The reddit history really tells it all. Constant downvotes, following people across subreddits, and constant yapping about Go vs C# going back years. https://old.reddit.com/user/_neonsunset/comments/ -- The intense religiosity and dishonesty with which you/you all discuss this topic is legitimately insane. Close the browser. Go do something else.
> The article mentioned nothing about stack security
I'm talking about the commenter.
> The reddit history really tells it all. Constant downvotes, following people across subreddits
I'm literally not that person you linked.
> I didn't mention C# one time in my post except for what was quoted in the article
Meet me halfway here, it's the language often used with .NET
> The intense religiosity and dishonesty with which you/you all discuss this topic is legitimately insane
I don't use C#, which I said before. You've been, from an outsider's perspective, corrected a few times. But I am no expert. I was trying to hear your thoughts about some topics in depth and you made it about as weird as a person could, so kudos to you. This is what I mean by weirdly hostile.
I did, it’s a thingy that creates tasty looking cloud objects (“canaries”) in your AWS account and then monitors them for suspicious access seemingly by slurping cloudtrail logs. C seems like a horrid language for this kind of very integration / API / glue heavy domain.
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.
reply