In my experience, C# and .NET are both "underappreciated" in a large part based on the legacy.
Some folks out there still think .NET is Windows only. Some folks out there haven't looked at C# since the .NET Framework days. Some folks out there are still salty about some of Microsoft's decision around OSS (debugger, hot reload fiasco, etc.)
But day to day working with C# and .NET for backends? It's really fantastic. AI seems to like it a lot and a lot of tools for agents to go HAM. Roslyn analyzer, source generators, workable hot reload (for a compiled language), tons of really good docs from Microsoft, generally low variance and high consistency from codebase to codebase (e.g. encountering Drizzle vs Prisma vs TypeORM vs Kysely vs ... in Node land).
My previous team switched from TS to C# and F#[0][1] and it was a blessing; a lot of the platform ops have been simplified. It's just one very solid tool chain and packages are generally stable (a lot less effort on maintaining platform config/tooling and more focus on just building). First party packages get consistent security updates and C# doesn't have nearly the same level of exposure to transitive packages that one encounters with JS/TS ecosystem.
C# feels like a really solid choice in the age of AI coding because of it's general stability, strong static analyzer, insistence on types, and really good tooling. Not right for all use cases (e.g. CLI, serverless functions), but fantastic for backend APIs.
If you're looking to do AI with/on C#, I wrote about some of my experience with making codebases successful for AI[2].
Being a Microsoft product is such a heavy baggage. Last time I checked the official debugger was still not open sourced and that alone was enough to make many people think twice. The fact that MSFT chose golang instead of C# for Typescript compiler, the state of Windows native GUI app dev... all erode C#'s reputation.
It's a good language though. The latest trend of hating OOP is unjustified/overreaction. I do write Rust too and I am not really convinced that traits are better than shallow inheritance + interface-oriented classes.
> Being a Microsoft product is such a heavy baggage
Which I always found ironic because TypeScript (from the same guy that created C# even!), VS Code are both Microsoft. Microsoft also makes big contributions and investments in Postgres and Python as well.
It is weird now that you mention it. I worked in C# and .NET for a long time. In theory for 20 years. In practice I wasn't writing much C# after 2019 even though the team I led still used it.
I guess I... just got bored of it, and fed up of Microsoft. I came to hate Windows and having to work on Windows so much that it sort of tainted C# for me. I'm not saying I'd never use it again but, whilst it will run cross-platform nowadays, so will loads of other languages and frameworks, so it's never at the top of the list when it comes to choosing something for a new project.
As for TypeScript? Absolutely loathe it. I'll use it professionally when I have to, but I'd never use it for one of my own projects. It's just so ugly I don't even like looking at it. I get why people like it but, for me, it's not enough. If I'm staring at something all day then, I'm sorry, it can't be ugly.
But, strangely, I still use and enjoy VS Code from Microsoft, and have no plans to switch to anything else.
(I also still have an Office subscription - or whatever it is Microsoft are calling that suite nowadays, I don't care - because I still use both Excel and Word fairly frequently. I could probably get by without, but I actually quite like Office - even on the Mac.)
I just write my TS like I write my C#. They are similar enough, IMO. I ended up writing this as a precursor to our team's move from TS to C#: https://typescript-is-like-csharp.chrlschn.dev/
The main things I don't like about TS are the volatile tooling, bad habits from other devs (not declaring types), compromised ORMs, require lots of package hunting, and module system.
I needed to be on Windows for work though and, sadly, often still do, so the association is strong.
The thing is, now most things I do target some flavour of Linux - and although I could write C# and target Linux with absolutely no problem - there are a lot of other options that C# has to complete with. Granted those options would also, at least in theory, have been viable options on Windows but it definitely would have felt a bit like paddling against the flow to host Ruby on Rails or Python or whatever on Windows.
And if I want to target command line or desktop with something cross-platform I'm going to compile to a native binary that requires minimal dependencies, so Rust feels like a more natural fit than C#, especially with Microsoft's desktop UI story being such a dog's breakfast (and even more-so when you account for cross-platform).
Again, in a commercial environment, I could imagine using Electron for that sort of thing because that's what most companies do (but because everything I do nowadays is backend, web, or systems integration, I haven't written a desktop app in a commercial context in 13 years, and I haven't written a command line app in that context since just before the pandemic). However, for my own projects I want to hew as closely as possible to my own principles and ideals and, for them, it'll be a cold day in hell before I ship a desktop or command line app that needs some giant runtime like Electron as a dependency.
Again, the point is that C#, whilst in many ways a great language, doesn't come out as the frontrunner.
Yes, Anders Hejlsberg did various times in podcasts when they announced the move go. It has nothing to do with C# not being capable enough, but rather that a line-by-line port from Typescript to Go would be easier. I think IIRC several time that had LLMs be more advanced as they are today, that choice would have been a different one.
Go models aren't easier, but the existing TypeScript codebase leaned heavily toward structural types, which made it a better match for Go and at least initially made porting easier for them.
JavaScript is one of the most common compile targets. "Who's the better JavaScript" was the most contested field a decade ago. These are not just toy languages - Facebook has Flow and Google has Dart. Both companies are far more web-adjacent than Microsoft. Yet Typescript won. It won so hard that we feel it has no competitors today.
Speaking of PostgreSQL and Microsoft, I was at at a presentation earlier this year and they are developing an underlying storage engine for it based on Azure Blob storage to get “instant” WAL replay across many replicas at once.
It was pretty impressive work to scale PostgreSQL.
On the historical (And semi-current-ish) downside we had/have;
- lots of 'flash in the pan' stuff between 2008-2012ish (Silverlight comes to mind here)
- Anemic Entity Framework in pre-core, and the weird stuff EF Core had going on early in it's life.
- That constant worry as to whether you need to add .ConfigureAwait(false) to every async call in a library you want to open source.
- The thing where trying to do 'traits' in NetCore (Now that you can mostly mimic the behavior) has had some side-effects on performance, mostly resolved but still something you have to consider as far as impact. (To be clear tho, thank god, needed that...)
- WTF was that UWP workflow?
- Why can't we get a proper designer? I don't think it would be that hard especially given I can do nice things around workflow designers today. Speaking of workflows...
- The complete (what I probably guess was MS groups eating their own tail) thing where Workflow Foundation was almost truly productive, SSMS had a thing based on it that was surprisingly productive, and it has been murdered instead of getting the TLC it needed to really disrupt. This one is extra loud because it is so similar to the 'low code' platforms so many orgs have sank money into.
- Less impactful as far as people eating the lunch microsoft left on the table, but WCF.
The upshot is;
- If you're willing to do some looking there are great options out there. lazy example, linq2db is basically a DSL that is close enough to EFCore as far as 'how you compose' that you can teach it to a group of WITCH devs in 30 minutes or less.
- It really is easy to do all sorts of stuff. It's just productive.
Every company I've ever worked for used C# to write business apps. The software that handles a huge fraction of all closed captioning of telephone calls is C#.
Also they dropped any pretence of doing any long term support, which is going to create all sort of interesting CVE over the long run given that it is mostly used now for backend.
And I am still not over the many breaking changes when they dumped full framework. Have to support a large code base, I think only now with AI will it be economical to redevelop it for .net core.
The decision to use Go for TS7 compiler is even more baffling considering Go's low level memory management functionalities is nowhere near the level of C#'s Span<T> etc, especially since TS and C# are invented by the same person.
I got so worked up by it I built my own TyepScript compiler with my clankers in C# to prove that it can be done, my revival of the Common Language Runtime concept, project Copeland:
Currently, Copeland compiles a dialect of TypeScript to JavaScript, C#, WASM via Blazor WebAssembly, and NativeAOT, runs in csproj, and uses xUnit.NET for testing framework.
(The other reason is that Microsoft still refuses to do the obvious thing in .NET 11 and take the best part of Rust and let enums be `match`ed, despite the fact that both TypeScript and C# have weak enums historically, so I added payload enums to my TypeScript dialect because Microsoft won't.)
You are right about the pattern matching, so it's on me that I didn't write clearly enough, what I meant is that I wish C# had Rust's compiler enforced exhaustive pattern matching over enums and the ability to carry data payloads:
I love C# and F#, but every .NET shop I worked for was full of MS cool aid drinkers and enterprisey nonsense. I don’t miss the culture at all. The languages are excellent, though.
Going to r/csharp over on Reddit is such a weird experience coming from any other background, save perhaps Java. A lot of people just yolo'ing with MockFactoryAbstractFactoryFactory abominations and injecting dependencies into your dependencies so you can inject your dependencies.
A lot of people also seem to have bought into the "Clean Code" dogma popular in the 00s, completely still living in a world where RAM is cheap and JITs are magical.
Add to this the absolutely miserable state of Microsoft's official .NET documentation, and it's a bizarre experience. Everything is "documented" - with things like "Foo(): Constructs a new instance of the Foo class", in case you were wondering what a constructor is...
Context: I'm coming from a background of C++ and Rust. Especially the latter leaves .NET's documentation in the dust, but that doesn't mean the CLR isn't a piece of extremely solid engineering. I'm continually impressed by the .NET JIT and GC, for example, and when you dive into the poorly documented details of the standard library (it's open source these days), you'll see that the quality is very high almost every time.
So yeah, I agree that .NET and C# has everything it needs to be really great in the right hands, but many hands aren't super right.
Is there any book that you'd recommend for modern, sane C# development? I've inherited a .net project and coming from Rails, a lot of things seem plain weird.
I don't know man... Rails seems fairly unhinged to me as well, but it's been a few years since I touched it.
My recommendation is always to understand the technology in depth, then make decisions based on what makes engineering sense. Understand the tools first. For me, that's a hands-on process, but others learn better from books. A lot of programming books seem a bit ideological from a methodological point of view, often written by people who thought they found the magic bullet (see the "Clean Code" fiasco) with perhaps limited time in the trenches.
I still choose .NET as the go-to whenever I can. On Windows or Linux.
I think the baggage view was valid for a while - but now we have a new generation of developers who only know about .Net Core and may not associate the Framework years in quite the same way.
I've been writing .NET code for 25 years too. I've tried a lot of other languages and platforms. Every time I've come back to .NET. All my web apps run on bare metal Kestrel under Linux which has come such a long way. I write all my Windows desktop apps in .NET too. Very clean code, easy to read, easy to write.
I wrote C# non-professionally long ago and loved it.
I recently tried to come back and the problem isn't the language. It's getting into it. Try to click "new project" and you're met with dozens of templates, and a lot of them sound identical to the untrained observer. Which web project should you start with? Which GUI project?
I pushed through it and made the wrong choice a few times for each of those. Apparently there are stable old one, and rapidly changing and underdeveloped new ones, and no way to tell them apart other than knowing the magic incantation.
Microsoft would do themselves a major favor if they cleaned that up and made things clearer, including definite guidance on why you should pick one template over another.
Instead, they treat them all as if they're perfect and newcomers have to just guess.
And no, web research doesn't help. There are tons of people who love each of them, and only a few people willing to write well-reasoned articles based on their experience, helping someone pick the best ones to start with.
I know this is not really the case for many others, but for me it's because there's a lot of set up; that's usually done by IDEs, but I'm unlikely to get an IDE before I've kicked the tires a bit. Go is run "go mod init", or hand write one, or even skip that step to write a single file thing. JavaScript is similar, you can start with one file, or start with a relatively simple package.json. Python / ruby / Perl / PHP is just the one file.
As far as I know, C# and friends still require setting up a project file, and there isn't a tutorial that has one you can easily copy.
Maybe this is why I also can't get into Android development.
(It also doesn't help that my Linux distro doesn't ship the toolchain because it can't be bootstrapped from text files, as far as I understand it)
This used to be true but is very much false now! The CLI can create any new project, even complicated ones. You can create your own project templates if you want. You can even start a solution and project files in an editor. It’s all just xml!
A lot of this changed with .NET Core and later numbered .NET releases (currently 11 as you see from this thread :)
dotnet new webapi
dotnet new console
dotnet new <template_or_sdk_here>
dotnet run
dotnet test
The tool chain has matured significantly and stabilized and it's quite easy to use. IMO, much easier than building backends with TS (the madness that is tsconfig.json) and getting configuration correct with TS tool chain (some easier than others).
I recently started a new project on .NET, mainly for its batteries included approach, seemingly a bit more serious ecosystem and the tooling AI agents can leverage during development and ops. Really happy so far, but too early and too small to have meaningful conclusions yet.
I'm especially interested in exploring F# as a concise, non ambiguous way to communicate definitions between humans and agents, somewhere between writing code like the olden days of yesteryear and describing things in English prose.
If you're going heavy into AI coding with C# (like approaching 100% AI code), give my 5 part writeup a read. Battle tested configuration with a fast moving team that was pretty much 100% AI code with good code quality. The configuration for `csharprepl` in particular is a special kind of unlock; having the agent be able to reach into the running DI container, wrap/replace functions while app is running just unlocks a lot of capabilities for the agent. It's the difference between an ultrasound and reaching in and manipulating the organ.
Oh, thank you, this looks really helpful at this stage!
Yes, the plan is essentially 100% AI code with a small team of programmers and domain experts from other technical backgrounds. But still depends on a few things on the business end before I can really kick it off. More of an exploration until then.
But the gist of it is that I wire it up through Aspire (don't need to, but makes it nice) to set the environment variable on my main app host. Then the agent can run `csharprepl` commands directly via CLI and read/mutate the running application state directly via the service provider (DI container)
I recommend you "train" the skill like I do in my the blog post. Basically ask the agent to get an instance of an object from DI like a repository class or your DbContext and have it perform some operations. Have it wrap a function and add logging. Have it replace a function. Then once it learns how to do this, have it write the skill bespoke to your project.
I strongly recommend that you try it "by hand" one time via blog of CSharpRepl author: https://fuqua.io/blog/2026/06/injecting-a-csharp-repl-into-a... If you try it manually one time, it clicks what the agent is doing and why it is so powerful (because the agent accesses and modifies the running application state without a compile).
Yeah. I fell in love with it about 15 years ago during undergrad at an internship. Built a few of my senior projects in it, and have made a career out of being good at it. It's been such a pleasure to see it turn into a general purpose non-MS locked language that i can run anywhere and do anything with, and it only gets better every year.
Thanks for the links. Your agent repl thing looks cool as hell.
Same experience here. My side project's backend is .NET 10 on Linux, in Docker on a single Hetzner box. It polls the now-playing line of ~58k internet radio stations every 20-60s and serves it as an API (tuneping.com). It's one process for all the background work (sync, polling, matching, webhooks), one for the API, and one Postgres. The thing I'd point at is the "low variance" you mention: hosted services, config and logging all work the same way, so there's very little glue code I had to invent.
I generally agree but so far every place I've been that had a sizable .NET codebase were stuck on Framework and so in reality it was very much Windows-only.
It's certainly not a big topic of discussion online but it's powered all of the healthcare infra my career has been around. There's a lot of "dark matter" code written in C#.
I pretty much exclusively use .NET with AI for backend stuff and run it all in linux containers. It's really good. I generally try to stick to stock standard .NET with only a few popular packages where it makes sense. You don't actually need too many extra things as there's quite a lot built in. Even for CLI its ok, though it won't be the smallest binary.
The thing I do not like about modern .NET is that Microsoft completely switched to AI-generated garbage as their documentation. This may be ok if you only interact with it though agents but painful to read otherwise.
I'm curious why you would consider C# not right for a CLI? There are some jagged edges with xplat support (e.g. System.Environment.SetEnvironmentVariable had/has issues on Linux, haven't checked recently), but IMHO its pretty solid for composing CLIs.
It's "OK", but the benefits of Rust or Go are undeniable here: faster startup, smaller package, likely lower memory footprint by default.
You can get C# to match Rust or Go, but those would require going off the beaten path and you might as well use Rust or Go.
I think C# CLI for internal tooling that is only used by an existing C# codebase makes perfect sense. As a standalone tool that's being shipped for multiple platforms, it would be better to use Rust or Go since those will give smaller native binaries and default better performance profiles in speed and memory usage.
The fact that when most devs look at C#, they can only see the silhouette of Microsoft is kind of making me mad sometimes, but I think it is understandable.
The corporate history of C# is undeniably strong, and even till this day in modern C#, they still have a lot of legacy components in it, like AppDomain, APM and MarshalByRefObject.
It is also unfortunately that C# has largely lost in the AI race, that it failed to gain tractions from AI developers despite heavy investments from Microsoft. Early AI stuff like Semantic Kernel actually included C# as its primary languages besides from Python. Nowadays it is not mentioned so much and the default is just Python but perhaps it really isn't a fair comparison due to the gravity of Python in AI as a whole.
Yet for another fact that even within Microsoft, Rust wins over C# for agentic development, is a clear indication that C# has lost a meaningful place in software development as its former parent started to berate it as well. TypeScript is rewritten in Go rather than C#, and consider the fact that Anders Hejlsberg made both C# and TypeScript...
Sure, maybe Bing and a large part of Azure and Xbox services still have heavily relying on it, probably because of ASP.NET Core and Orleans, but outside of the two Microsoft greenhouses, it is a free for all battle royale where C# don't even have a chance of being picked at due to being too weak.
I even tried to contribute a C# SDK to the Restate project, but the maintainers actively rejected it due to them not being familiar and refused the handover. They said they will consider if there are commercial fundings backing it, then they would starting to consider (that's even with money attached they would still think about it). This also shows how unpopular C#/dotnet is among open source development and failed to build hype. The same Restate project even have a Java SDK for it, so it is not like they can't handle C#, they just didn't like to.
The last few thing C# is good at is just Unity I'm afraid, and that is also dying too as Unity is also not doing so well lately.
Rust and C# serve different purposes. For things like CLI tooling and the Copilot SDK (supports Go, Python, JS/TS, Java, .NET), Rust makes total sense.
For web API backends and MCP servers, I firmly believe C# is the better choice.
For AI use cases (as in make calls to LLMs), there's not much difference, IMO from Python and C# and TypeScript. At the end of the day, those are all just REST API calls and SDKs are mostly wrappers around it.
For LoB apps where the AI/LLM is just one small leg of the actual flow, C# is still a winner, IMO, because of how good the overall stack is (e.g. EF Core). Orleans (actor model framework) is also super interesting and I'm exploring some memory/communication system based around it.
TypeScript was "ported" to Go because it matched the closest & allowed easy line for line swapping. C#, F# or Rust would have required a re-write with architecture changes instead.
It’s also awesome for SREs / SysOps / Whatever We Call Ourselves Today:
Opinionated in a good way, which means almost all C# apps have the same parameterisation with environment variables, command line, and config files. It’s all very systematic, consistent, but flexible and pluggable.
“Node has that too!” you’re about to say. Lol, no. It has bits and pieces the devs have to wire themselves, which means a fleet of vaguely related apps made by independent teams will have wildly different config styles. Good luck consistently making all of them pull API client certs from KMS or Key Vault, or whatever!
OpenTelemetry is now baked in to the core components and rapidly spreading through other libraries.
Excellent “pervasive” system APIs for spans, traces, metrics, and logs means that the sources are omnipresent and very rich, going back decades. Even very old third party libraries are often a breeze to instrument.
Debug snapshots from production crashes on App Service feels like cheating.
Built in container build outputs with no extra tooling.
Sane defaults for its package manager means far fewer nasty security surprises.
Far more first-party “coverage” of essential capabilities in the optional Microsoft packages so you don’t have to resort to NPM where your security-critical code is written by Sergei and support is “LOL Wut!?”
A GC that automatically responds to container limits instead of the host limits.
It uses thread pools for async tasks in most configurations so whole categories of production-time issues that occur with single threaded runtimes just doesn’t happen.
AIs write pretty good F#, but there simply isn't very much of a reason to use F# over C# since Microsoft basically uses F# as an experimental test bed to test .NET features before bringing them into C#. Plus, annoyingly, F# doesn't interop well with existing C# projects which there is even less of a reason for existing .NET shops to adopt F#.
I also agree with this. When we were internally finalizing on C#/F#, I showed the CTO that most of what he liked about F# was already possible in C# (though requiring third party libs like OneOf or Dunet) with the benefit that C# syntax is more familiar for most folks. With 15, I think they are even closer now.
One of my favorite things about F# is what some would consider a deficiency: that within a project, files must be ordered and you may only refer to a symbol defined earlier, with the sole exception of types which are explicitly declared together in a single statement (`type X = ... and Y = ...`).
This in contrast to C# where, within one .csproj, there is no "order" and all classes and methods are able to mutually reference one another.
The F# way creates an annoying housekeeping step of maintaining file order, and would be a nightmare to do with manually edited .fsproj files (I have only ever written F# with an IDE), but IMO, it is worth it. On the reading side, it makes it easier to understand how a project you've never read before is structured, since you know the order of the F# source files also must be the bottom-to-top layering of the code. On the writing side, it sometimes causes one to rethink the architecture and simplify it to minimize the number of mutual declarations.
That might be true. But I still think code and ideas are more clearly expressed in a higher level language like F# still. Even though C# has adopted a lot of features from F# over the years, F# is still a lot cleaner to read and write with its excellent type inference and super minimalist syntax.
Seems very good at it; for a while in my F# project I didn't know how to tell claude code on the web how to install it, but claude still made code that worked fine - its COT said, paraphrasing: "no F# compiler here so I will check this code by inspection". Later I learned how to make F# available there - in claude.md tell it to install the ubuntu dotnet runtime via apt, which includes F#. May not be needed anymore though.
Motion shifted to C# around August of last year. Part of the reason, I suspect, is that the CTO (Hi Chander!) is an OCAML fanboy and F# was the most practical platform alternative.
We shifted all new product backend build to C# (because at this time last year, we were still "AI assisted" and deemed F# too foreign for most devs to read well), but all internal tooling was built on F#.
By maybe May of this year? We had multiple F# backend codebases, fully AI generated. Only one person on the entire team had worked with F# by hand. Several things work in F#'s favor even though it has a lot less representation in training: 1) the people that write F# really love F# so the code out in the wild is going to be better quality (hunch), 2) F# is a bit more terse/dense than C# and less noise in context, 3) F# benefits from the same tooling stack, 4) F# type system is still a bit more expressive than C#.
I know C# deeply so I know what good looks like and I know how to get agents to write good C#. F# I'm less familiar with, but we had great success with it.
when I realised I didn't have time to actually finish my own programming language (there was a big change needed in the type checker and it would have taken months with how much I can use a computer atm), I let claude something like 98% of the work, and it worked very well. The code comments are awful, but the code is ok. recently i have just let it have a go, and the code has grown more than I care for, but generally it has been doing well: https://github.com/bjoli/Bjolang
I've used F# on several projects, and right now we're building all our infra with it. AI does very well it these days.
AI isnt quite as creative and clever coders as humans though, but thats true for any language you direct it at, but its a bit more obvious for higher level languages like F#.
Built the massive enterprise backend of a super app fully in F#. From my experience, AI was able to write idiomatic F# code since Opus 4.8 (all models started doing very decent things around that period).
It's kind of bewildering for me that, despite all similarities between C# and TS (including their actual design architect), only one has received so much negativity.
Maybe MS can work out a longish-term roadmap, where in a certain future version C# and TS gradually "morph" into being the same thing (e.g. Typescript 10 == C# 20 or so)
> I think C# would be a great choice for both CLI apps and server less functions.
Really? I feel those are the two areas where it falls short. CLI apps and serverless both value cold starts and C# is not great at it because of the JIT overhead. The immaturity of NativeAOT ecosystem on C# was cited by Anders as one of the reasons the TS team chose to rewrite in Go.
I write all my CLI apps in C#, but I hear you on the startup time. I guess it depends what you are working on whether that little hiccup is a problem. I've never once noticed it with anything I've written.
I’d be curious whether a CLI app written in C# would compare to one in Python for startup time. Python isn’t exactly a speed demon, but I use it constantly.
C# was my 2nd programming language after VB6. I remember in the early 2000s when I was 12, using my best friends computer to try and download VB .NET (he had faster internet) just because it was free, then being extremely disappointed when I couldnt just run my VB6 projects I wanted to run on it... Some years later, during 2007 ~ 2009 I tried out C# and that was life changing.
I wound up going to college after working retail at Disney, then after college, my first job was supposed to be C# but they didn't have the project approved, so I got my first exposure to Python - which I loved, then JavaScript - which I hated but have a love-hate with to this day, and finally C#, but it wasn't .NET 3.5 no, they started using .NET Core, which was... something else, lots of teeth pulling during the original .NET Core days.
So many wonderful projects made with .NET my only complaint is I wish Microsoft would make a cross-platform GUI stack they'd actually stick to, and make it work on Linux too. ;)
I know there's alternatives but nothing beats telling your coworkers "lets just use the built-in" when choosing a GUI stack.
We abandoned .Net years ago before our third major product rewrite prompted by withdrawal of various bits of tech they said was the next greatest thing. AppFabric, WWF, WCF and Silverlight hit us hard.
That's fair enough, but the dust has largely settled on dotnet core. The death of Silverlight was long ago. I think the only thing at risk of abandonment is the native UI situation, which is a mess.
Hard to say what's native in Windows anymore. I imagine Win32 APIs and more recently Windows App SDK?
The most supported UI frameworks from MS for Windows are WPF and WinUI 3. UWP is dead and WinForms (even though closest to something native to Windows) is probably going to end up in the land of forgotten (sorry nostalgic Windows devs, you have to face it eventually).
A "mess" is an understatement when the people who themselves develop Windows desktop apps have trouble explaining the landscape.
So if I decide to code a windows app just using C Win32 api, will it outlive the current generations and be guaranteed to run fine(compatible) on future versions, while the modern alternatives will be stuck in a constant cycle of deprecation?
It's not quite that settled yet. Lots of third party libraries are getting abandoned pretty quickly. We are also stuck in purgatory due to a VSTO add-in which we can't rewrite in Office.js because it's nowhere near as powerful and there is no usable .Net Core integration.
I used to be in the anti .NET camp but for Enterprise applications .NET has been delightful and upgrading between versions has been trivial since .NET 6.
> upgrading between versions has been trivial since .NET 6
I don't understand this. I love C# as a language, but the idea that everyone is forced to upgrade every single library every 2 years is beyond unreasonable. I've recently run into a situation where some configuration behavior deep in ASP.NET just randomly changed between .NET 8 and .NET 10, and broke things. A runtime exception, not a compile-time one. What else is subtly broken in a DLL referenced by a reference of a reference? You may never know.
It seems like every software engineer has forgotten that code is ultimately math. All of the greats understood this, from the times of Alan Turing through the 1990s. Then all of a sudden it became some sort of a toy, a game for 20-year-olds to one-up each other, to move fast and break things. Somehow, as software development matured, it became less of a serious discipline. Now, we have an entire ecosystem where it's just accepted that any line of code you've ever written may just randomly break every 2 years. You can no longer just write a small C# library and expect to to continue to work forever. It's completely unacceptable in my view.
It sounds like .NET Framework is for you. Support until sometime after 2030 (or more likely "forever", because it'll always be shipped with windows server and inherit its support lifecycle).
That said, I'd second the assertion that upgrades after .net6 are, for the vast majority of applications, trivial.
> the idea that everyone is forced to upgrade every single library every 2 years is beyond unreasonable
That's overstating the case. A library that targets dotnet 6.0 will often continue to restore and run in a dotnet 8.0 application. The notable exceptions are framework-aligned packages like Microsoft.Extensions. Most of the time you just need to bump the target framework in the props file, check it builds, run unit tests, and upgrade a few library packages that might need it.
So about that... when I started using .NET Core, it was bleeding edge and shiny, but it was to rewrite a Silverlight application, the only way to run it was Firefox ESR 54 (I think it was 54?) we thought "oh this is easy" only to find out that it was a colossal nightmare, EF didn't support some things, including Shapefiles, we had to force the support, get back a WKT from SQL Server, and send one back when we did updates, basically Entity Framework was mostly useless.
I also reverse scaffolded the existing database into EF compatible classes, so that saved us a ton of effort, the original idea was to be backwards compatible.
Best part? It never got used, when we found out they weren't using the project, my boss said, screw it, lets remake it in Python I know we can build it up quicker, and sure enough, it was done quicker than the C# rewrite, fastest rewrite I ever did, because he figured out the key things they actually needed, and trimmed the requirements to just those key things.
Sometimes rewrites don't need to be 1:1 especially if it's an internal only tool.
Anyway, lots of blood, sweat, tears, stress, etc from me as it was my first job in tech.
Boy do I have good news for you (I am half kidding). OpenSilver project lets you compile your Silverlight code to Web Assembly or iOS/Android/Windows/Mac.
WCF had some good ideas and if you followed its best practices migrating to other networking stacks should have been straightforward. Delete your complex Bindings XML configs and just write implementations of your Contract interfaces and figure out where you need to dependency inject the new implementations. If you were deep enough into WCF you might have even had mock implementations you could bootstrap from.
That concept of "define an RPC interface and let the platform fill it" was ancient before WCF and continues to this day (I've got a bunch of tabs open for Cloudflare Durable Objects right now). The legacy problem with WCF was not but long term backward compatibility with the Bindings abstractions and XML config junk and SOAP Stack RPC. Without WCF native in the box in .NET 5+ it is harder (but not impossible) to build SOAP RPC monoliths. But so few businesses actually need SOAP and can't just switch to something more RESTful.
Silverlight was maybe ahead of its time given the growing popularity of Blazor WASM.
I'd be okay with this, I'd also be okay with MAUI (which... is a whole behemoth of how to make UIs), but anything, hell I would take them porting WinForms somehow into a cross-platform .NET thing that runs everywhere (Mono used to!), I just want anything at this point.
XAML was supposed to become an open spec, not sure why they have so many teams making so many different ways to make UIs. Then there's those "Windows 8" apps, or the "Universal Windows Apps" which are poorly named, you cannot run any of them on previous versions of Windows, if anything Win32 apps are universal apps.
Wow, what a vicious footgun. I do a lot of work related to pricing, invoicing, exchange rates etc... where decimals do matter, so this kind of stresses me out. Thanks for the heads up.
Yeah, while I do agree this change in behavior could be a major footgun for a few people, the vast majority of use cases for decimal should be unaffected. Decimal is usually used for business applications to the exclusion of double/float, not alongside them (the only doubles I've seen in my company's applications are just storing Lat/Long values for addresses, everything else uses decimal values).
Hell, on the rare occasion I've needed to use a decimal literal in code, I just use a decimal literal: don't do (decimal)0.1, just use 0.1m.
Exactly. If you want an actual decimal literal you should always write it as 1.2345m rather than converting it with the (decimal) prefix. The compiler refuses to accept a plain 1.2345 assigned to a decimal type so it’s not such a footgun as it may appear to some; though it might offer a better DX if the type of the literal was inferred from the thing you are trying to assign it to, that would no doubt have unintended consequences.
Fair enough, this is mostly what i do but since the runtime has been forgiving, i'll need to triple check that. I label all pure money fields as decimal, but am less confident for the ProductQuantity kind of fields. I typically use int for whole numbers but wouldn't be surprised if i used float occasionnally for quantities accepting decimals.
Ex-MSFT here. Some of MSFT’s biggest and high scale web services run on .NET Core on both Windows and Linux. I am speaking of services in Office, Security and Azure. Think across millions of VMs and servers. The tooling is great, the services are scalable. It’s nothing like .NET Framework.
MSFT’s own services run on .NET and it’s in their own interest to see it thrive and adopted even outside of MSFT.
My current employer, a mature US payroll company also runs on .NET core extensively and safe to say that the development is a breeze. Although I still find that I have to use a paid IDE like Rider or VS. The experience on VS code isn’t that mature.
"Cattle not pets" paradigms mean any of the main languages and runtimes I.e. Java, Node, Python, Go, Rust, PHP can achieve this with the right DevOps / SRE and system design. Albeit some may be more inefficient therefore expensive.
I always liked C#, but the state of IDEs on Linux is what keeps me from using it more. I tried to get a nice setup with VS code, but I agree it's just not good enough. I do use Rider when I work with C#, but I'm not really happy with it either, although I can't really point to the issue with it either.
Microsoft is doing great things with C# and .NET but I feel like the OSS community is struggling with its culture and identity a bit.
There has been a lot of rug pulling by some of the most popular open source packages in the .NET world in recent years.
First it was Identity Server, which Microsoft even recommend using in the official documentation (or at least did a year ago), which went closed source and switched names. Then there were others like MediatR and MassTransit who recently went commercial. And now, most recently, several packages are leaning into the "Open Source Maintenance Fee" model which is technically still open source but problematic for any who updates their nuget dependencies without reading all the documents included in the package. I think it hurts extra much because in many of these cases they were very much embraced not only by the community but also by Microsoft just like Identity Server. Like Polly is actually used as a dependency by official Microsoft packages after Microsoft decided to embrace it rather than replace it with their own implementation. Similarly MassTransit and other similar projects objected loudly a few years back when Microsoft were proposing their own messaging/eventing framework and Microsoft backed off.
I'm not saying that OSS maintainers and projects don't deserve to get paid, but none of these projects would have become nearly as popular had they started out commercial. And I feel like this type of thing is happening a lot more in the .NET community recently than in others (although I will concede that I am not as involved in others).
Perhaps it is the fact that Microsoft as a big enterprise entity is very much in the driver's seat of the stack or the fact that it is used in a lot of enterprise software or just cultural baggage from 20 years ago where you paid for every library. In any case it feels like there is quite an Enterprise-y sentiment within the entire .NET community. Unfortunately.
Awesome stuff, as usual in .NET releases! The runtime async support is surprising to me... I get that stack traces are more friendly without state machines in the code, but I didn't expect they could switch to a runtime version and have equivalent performance. Cool!
They don't need to mark functions as needing special compilation / rewriting to support asynchronous use since the runtime provides the necessary mechanism to pause and resume (e.g. stackful continuations). So 'async' is unnecessary and 'await' can be a library function (or remain a keyword of course).
Friendlier stack traces have an important upside: Cancellation is done by throwing exceptions, but you can't stop it from storing the full stack trace in a string. This can result in a noticeable hitch if the stack is very deep and complex. With a friendlier stack trace, I hope this issue is finally gone.
As the article mentions, stack traces in exceptions are the one place where this change doesn't really move the needle: they were already hiding stack frames related to the async machinery in exception stack traces. The main benefit here is the live call stack in the debugger.
> One especially useful result is a clearer live call stack. Profilers and debuggers can show the asynchronous methods themselves instead of the infrastructure used to execute them. This is a distinction from exception stack traces, which already hide much of that infrastructure.
Maybe exceptions will still gain some benefit because it doesn't have to physically walk so many frames? But the string at the end is likely to be roughly as big as before.
Seems to be a gift of the modern CLR cycling faster with each release since .NET 5 versus the backwards compatibilities the runtime used to have to keep in .NET <= 4 because the same runtime would power multiple versions at a time of the compilers and BCL above it. In the old world "Framework" days after the CLR made the huge changes from 1.0 to 2.0 (for generics and other things) it seemed almost "afraid" to add features, especially for things that compilers could support themselves. The .NET 5+ CLR makes fewer "forward compatibility" guarantees as it now assumes it cycles directly with the compilers and that most deployment methods "bake" the CLR version with the application.
There have been smaller runtime improvements like this one in recent releases, but this one seems a big one to point to to prove that the runtime is truly growing at a faster and more interesting pace than the old days.
We've pragmatically switched all our apps to .NET MAUI and it's been a good ride.
Starting from native mobile apps, we shipped a Windows version as an add-on, and once feature-complete we switched the mobile native apps to the MAUI version.
The debugging phase has been surprisingly short and the app quality is on-par with native.
Too bad Apple never wanted to open Swift to Android, because now .NET MAUI is what Swift should have been.
I have done the same as I already had windows version, but customers were asking for mobile versions as well, so MAUI has been the eventual choice.
Framework has been really immature 2 years ago, but today it is pretty much stable and works fine. Deployment is little bit opinionated than the the WPF builds, but the MAUI keeps improving.
> .NET 11 is a Standard Term Support (STS) release with two years of support under the .NET support policy. STS and Long Term Support (LTS) releases have the same quality and servicing policies; the difference is the length of support. .NET 10 remains a supported LTS option. Stay current with servicing updates whichever release you choose.
They burried the lede on this one.
- .NET 10 is end of life'd on Nov 14th, 2028
- .NET 11 is end of life'd on Nov 9th, 2028
Which means anyone who upgrades to 11 has fewer days of support. But the idea is that you'll upgrade to 12 as soon as it's RTM'd.
The problem with that is I have clients who refuse to budge from 2.1 (or even upgrade to the 2.1 re-release: 2.3).
Nothing new to comment on here. MS has been open about its long term and short term releases based on even and odd numbers. Unsurprisingly, there's a web page that explains it https://dotnet.microsoft.com/en-us/platform/support/policy
This whole thread is a bit of a tangent, but I'm not sure the STS/LTS distinction makes much sense for the end user. STS gets a relatively generous 2 years of support (cf. Java at 6 months) while LTS gets a relatively anemic 3 years of support (cf. Java at 8+ years [1]). Plus, with yearly releases, every LTS expires at the exact same time as its successor STS. Are STS releases generally less stable or more experimental than LTS releases? Otherwise, this feels like a valueless distinction.
Supporting anything for 8 years is a huge commitment. That means 8 concurrent versions needing at least some work all the time.
I think Microsoft regretted those long (10 years) support guarantees they had for .net framework. In the end, it probably slows you down in terms of innovating
Given .NET's release cadence, 7-8 years of LTS would mean 5 concurrent versions: 1 STS release and 4 LTS releases. This is still a lot of old versions to keep updating, of course.
Anyway, the critique was less about the exact length of LTS, and more about the lack of significant difference between LTS and STS.
That may have been true in the past, but that is not the stated policy: "The quality of all releases is the same. The only difference is the length of support."
Well, yes, that's the official narrative, but if you seen .net 5, .net 7, and .net 9, and .net 11 and compare them to the LTS that came after them, it's easy to see that pattern.
.net 5 brought records,and then .net 6 brought record structs.
.net 9 previewed the field keyword, and .net 10 released it.
.net 5 brought primary constructors for records, then .net 8 expanded that to classes.
There's a few opposite examples too, but it's a trend I felt over the past decade.
My only wish is they just jumped C# and .NET versions to the year number, since they are incrementing annually anyway, and aren't do semver anymore.
If you mean ASP.NET Core 2.1 or ASP.NET Core 2.3, then they are actually .NET Framework despite the name, and are not the same lineage as .NET 10 and .NET 11.
It's not that complicated. ASP.net Core 2.2 and earlier was targeted at .NET Standard, so could run in any CLR (CoreCLR or Framework or even Mono, though I don't know how supported mono was). Asp.Net Core is actually just a csharp library like everything else.
C# and .NET are absolute joys to program in. To me they are a slightly more "enterprisey" Ruby on Rails but once you're in it there is a flow to it. Things fit very nicely together, especially with LINQ. Runtime code compilation/"scripting" with Roslyn Compiler Services et al is just great.
I used to use Rider, but its slow and clunky with too many bugs now. It's big advantages were all its code fixes, refactoring etc. These days with AI on linux I just make the AI environment the IDE and use vim as an editor when I need it. Haven't opened Rider for 8/9 months now and stopped my renewal for it. .NET is super easy to use from a command line, so you can do whatever custom set up you like. If you really want an IDE, you could go with Rider, I believe they have a free version now for non commercial work, but I feel like the days of IDEs are kind of numbered.
Same experience. Used Rider exclusively for maybe 8 years, all of them on linux, but clanker advancements in the past six months finally allowed me not to open it at all recently. I believe I even removed it from disk.
Good IDE but they ignored bugs and serious performance problems for too long. The main project I'm working on isn't even that big, maybe 1M SLoC in total, most of them C#, and Rider really struggles with it even on good hardware.
This forced some architectural changes to satisfy the machine (breaking out libraries where they weren't necessary, running code analyzers only in CI, not during development, etc).
So I don't miss the experience.
MS finally have a language server (still in preview, but it works fine) which can be attached to helix in a couple of lines, and that + good knowledge of Unix command line tools is more than enough now.
Visual studio still feels like the only real option to me. I would consider switching to windows if it meant I could use it.
There is a gigantic difference between "technically possible", "practical" and "enjoyable". I had some junior engineers hounding my ass about using ORMs and the liability of manual schema management until they saw how sqlproj and sql compare work in VS proper. You can certainly string this along with 3rd party and custom using other IDEs, but then you don't have as much time and energy to focus on the actual problem. Maintaining a stack just to maintain your stack is pretty crazy to me. I'll happily pay the idiot tax at Home Depot for the Milwaukee option so I can place an invariant on this part of the problem space. I never want to be in a position where I can blame my tools for a bad work product.
I switched to Mac and tried Rider for a while but ended up very happy doing everything in VS Code. I guess in the pre Agent days I would have put more effort into learning Rider but in modern times I just don’t need so many fancy tools for manual refactoring etc. But if you’re still very hands on, it’s an excellent - and possibly superior - Visual Studio replacement.
Claude and codex ? Half joking, but can't remember last time I bothered booting up Rider - probably over 6 months - will unsubscribe next year. VSCode is good enough for exploring changes, LLMs are driving the CLI tooling anyway.
I moved from PHP to C# jobs because thought it will be more fun.
Have to agree, PHP codebases also were nice organized, bigger repos, mostly web apps / company backoffice apps and so on. Personally, products were more interesting.
With today dotnet state, it really suits well for API development / microservices / integrations / we had many workers - jobs running, queue subscribers, K8S, good test coverage, open telemetry.
Even migrating from .NET Framework wasn't so big pain.
Does runtime async mean stackful coroutines? It's not obvious what exactly the state machines are replaced by, if not automatically inserted yield() calls.
I took it to mean that coroutines are still stackless but that yielding is now an operation the runtime knows about instead of being compiled out by the compiler.
For the features that overlap between golang and .Net, can someone please let me know why people choose .Net over golang for non legacy systems? As far as my limited knowledge goes, go is much more efficient in syntax and memory.
The languages are very different. C# has much more powerful abstractions and is a much larger language in general. That is both an advantage and a disadvantage. Modern C# is also very fast, I wouldn't bet on Go winning that comparison.
My experience with Go is a while back, before it got generics. While I liked the overall idea of a language with little syntax and a focus on simplicity, I didn't really like Go in the end. It was a weird mix of too low-level and high level code. It exposed low level details that I don't need to think about in C#, and it is missing convenient abstractions like LINQ.
Personally, I like async/await in C# more than Go concurrency. I know this is heretical here, but I don't care about function coloring or any of the abstract advantages of other concurrency approaches. Async/Await in C# works for me (in non-legacy code) and is easy to understand.
the main problem with the .net async/await is the immediate execution of a call returning a Task, without control over where it may execute. F#'s asnyc was way saner and easier to understand in my opinion.
I'd beg to differ on the easy to understand... last time i worked with this stuff like ConfigureAwait only has cargo cults and no real good explanation, and wasn't covered in any recent version of eg. CLR via C#... Distributed tracing and thread management were totally bitten by async, also the way iterators and LINQ were broken... that was when I got off the train, as it broke the best abstractions for line of business code so that instead of designing an IO pipeline lazy developers could simple put Task.Run around blocking IO calls at the bottom of the codebas, and ... yeah, we are back at function coloring.
This brings me to: I winder for what kind of apps did you find it a delight, because for extensive data wrangling leveraging multiple calls it was a pain.
You don't need ConfigureAwait() at all in modern C# (unless you're writing desktop UI code, I don't know enough about that to comment there). In a modern ASP.NET Core web application you don't care about which thread your Task executes, there is no synchronization context at all.
I'm using C# for the backend of a web application, not legacy but .NET Core from the start.
Indeed, you need a crapload of ConfigureAwait() in desktop UI code, and it infects all underlying libraries that might have to run inside a desktop app. The worst thing they ever added to our beautiful language. It's a dream to work in ASP.NET without needing to pepper every await with a ConfigureAwait().
That said, I don't agree with the characterization of ConfigureAwait() as un-understandable. We understand it. We just hate it.
In case you were not aware, you can use Fody to automatically add ConfigureAwait everywhere it's needed at build time: https://github.com/fody/configureawait
Sometimes you do care where it executes, and then it starts to become a problem ;).
Something which was as easy as an IO call in the middle of a function running on a thread pool becomes a PITA if the IO call is cleverly async-ized (by wrapping the original sync IO method into a Task.Run(), but actually this is the better case!) ad then removed from the public API... (cannot recall the exact library, but it was a pain, as it was some core stuff, maybe vendored by MS themselves)
Also back then it caused us loosing correlation IDs as those were thread locals in traditional pre-async ASP.Net Core (think version 0.9-2.0 when I worked on that part, then up until core 4 and the async mania continuos broke readable calculation code in LINQ, TPL based performant data pipelines, etc...) and with async execution started to migrate between threads... If you needed high performance you already used threads, because it was not a classic web application, but a specialized optimization algorithm with a web api... and if you need more than CRUD, and use all the cores (and SIMD) to squeeze out all performance you have, you hit these problems right away. These all could be solved, but it actually broke stuff, a lot of stuff, as developers jumped on the fad, and it their sync code was turned into a .ConfigureAwait(false /* Microsoft blogs talk a lot about it without a clear explanation, so every cool kid does this */).GetAwaiter().GetResult()... then you some of your threaded code silently broken if you dare used Thread Locals... Faded memories, but that was when they f'ed up the C# language.
That's why I emphasized modern .NET. All I/O should be using async methods through the entire application. Wrapping sync code in async causes troubles, but that is entirely avoidable in new code. That's the price you have to pay to make this particular abstraction work well, everything I/O must be async.
Based Hopac enjoyer. I built the Spiral's language server using it. I have to admit though that these days I just use native .NET channels for all my message passing needs.
I vastly prefer cml, but hopac has a lot of missed opportunities. I vibe coded my own language on top of c# that has a cml that is much more integrated with the language (and no other constructs are available :)). Being able to use events for things like reading from ports, so that you can select over reads and only the winning read actually takes anything from the port is more than nice to have. Or having STM that can be run as an event, so that you can choose over a transaction or a timeout in extreme congestion. Since it is composable, you can abstract away a lot of the details behind simple function calls.
I am kind of curious if it becomes faster when moving to dotnet11.
Go is not much more efficient, compared to NativeAOT - though it can be faster in some areas. The big reason why someone would want to use C# is that it's a more expressive language. You can even write your DB queries with LINQ (C# lets you express code as data), making them type-safe.
Modern .net is surprisingly performant while also being very high level if needed. So it is very close to being a "learn once" ecosystem. This means you use it to write code for a much broader range of usecases than others, you will have a broader / more mature ecosystem than others, and your potential hiring market is more vast.
Migrating to Go was easier because it was easier to port the code instead of doing a rewrite. Go matched the easiest to the way the code was written. C#, F# or Rust would have required a re-write instead of just simple changes.
Go still has a better deployment story. Single static binary deployment are king. On .Net 8 I was hitting random compatibility issues with single binary application bundles, with System.Data.SQLite, and other libraries. (had to use that one and not he Microsoft one for some legacy compatibility reason, maybe around Guid handling maybe)
From your description you certainly weren't using AOT compilation, and forgetting about CGO.
Static linking as the only way in most platforms, we moved into other mechanisms for a reason.
In Go the only way to do plugins, without shipping everything as source code is to use microservices, the more fashionable name for multiple process with OS IPC.
I'd presume you can still do dlopen despite your binary being statically linked... but I have not expermented in the topic, I did dlopen in Linux libc, and Assembly.Load and P/Invoke in .Net (no AOT).
I wasn't using AOT, it was not something I needed/wanted, but for a TypeScript compiler it would make more sense. I only tried single file binary compilation, as I didn't want to deploy the runtimes, wanted to have them embedded to the air-gapped systems in a simple copy and runway (no installers, etc).
Yeah, but that beats the purpose of static linking in first place, especially if the plugins require access to the host process APIs.
Doable, but requires a bit more of extra work, and Go plugin package is basically abandoned, it was not yet been removed due to backwards compatibility.
In .NET 5+ you shouldn't need to install runtimes, though. Self-contained folder full of DLLs output is the default publishing build today and very reliable at this point (and doesn't have the same restrictions as trying to AOT compile and treeshake everything into a single self-contained EXE).
Obviously, there's a trade off in size having a whole folder of files, some of which would be redundant with other applications, but folder of files using xcopy only deployments have seemed to become the standard in .NET. (Or perhaps more commonly, folder full of files added to a minimal "no runtime" Docker container overlay.)
I don't think I've ever seen a global install of a .NET 5+ runtime. I wouldn't be surprised if some Enterprise was doing it for some belief in HD space savings, but I've certainly not seen it. Just file folder deploys.
with Go, they have made one commitment. The language will always be backwards compatible. So code written in today's go should still be able to be compiled in 30 years using the compiler of the day. That means when adding new language features, they are very, very, very particular and stubborn about it.
Every mature programming language places a very high priority of backwards compatibility, including C#. Microsoft introduced generics, LINQ, async/await, pattern matching, records, nullable reference types, and numerous other features while maintaining a very high degree of backwards compatibility.
But either way, this merely provides an excuse as to why Go is actively hostile to it's users. But backwards compatibility and language evolution are not mutually exclusive.
I know it's not quite as important in the agentic world, but the .NET tooling (across Visual Studio, JetBrains Rider, and C# Dev Kit for VS Code) is just awesome. Some people might frown upon full-blown IDEs, but sometimes they're just incredibly handy.
Every new release of Go brings some of the features that have been available for twenty years in .NET (and every other language), like generics. Some features, like ergonomic error handling (whether or not you think unchecked exceptions are a good idea), Go has said will never be available. There isn't much to recommend about Go. It's fast to do the mediocre thing, faster than any language, but for a serious code-base intended to be maintained long-term, C# has better libraries, established patterns and frameworks, abstraction features, developer ergonomics, etc.
Put things like their remote debugging experiences side-by-side and you will quickly learn why some of us go for the Microsoft tools rather than the others.
Exactly C# with .NET is batteries included type of ecosystem where most of stuff is done by Microsoft and if someone is experienced with the stack he can whip up enterprise ready app in no time.
I don’t have much experience with Go lang but what I have seen, there is lots of things one has to implement on his own. Which has its own merits, but to go from 0 to full fledged enterprise app you need more time.
I'm not a .NET person by any stretch and have been more of a Go person traditionally, but I think from what I've seen IRL, this is accurate.
Go is trying to be like C. Less language features (until recently...), so to solve problems you write more code. Intentionally simple language. Intentionally need a lot of code to implement something substantial. .NET seems more frameworks driven. Go traditionally has eschewed frameworks for doing stuff much like C tends to eschew framework type stuff and prefers smallest dependency reliance as possible.
Go is also very much a batteries included language when it comes to their standard library. Like it even comes with an ORM. Most people use the Http server that comes with standard library. even more so now that the router for it is a lot better.
Compare the ecosystems of .NET vs golang. No comparison whatsover. IoT, mobile, web (front and backend), cloud, etc. I think Go lacks variety and is more focused on traditional greybeard areas.
There's one (usually minor) caveat here, which is around strings. In .NET, like Windows*, strings are UTF-16. If you call into native code that uses UTF-8 strings extensively, then they have to be marshalled (when passed to native code) and unmarshalled (when received from native code), which takes additional time and memory. I've run into this lately, which has led me to write special CStr/CString types (idea borrowed from Rust) to avoid unnecessary, especially round-trip, (un-)marshalling.
* = Technically, Windows now supports the UTF-8 "code page" where applications can use UTF-8 natively, but all NT system calls use UTF-16, so the conversion still has to happen somewhere
C# in .NET 11 adds support for UTF-8 string literals (`ReadOnlySpan<byte> str = "Some sample string."u8;`) which is a step to making such interop easier.
The Runtime has explored attempting "UTF-8 native" string storage as a platform feature especially for Linux FFI, but yeah Windows using UTF-16 is still a compatibility issue where platforms themselves just don't agree.
For me, I use both languages. But there's a lot of things which are only like a couple lines or very easy to do in .NET but to do it in Go is a lot of work.
Some example.
Open API document generation using type handlers doing this in go was a pain in .net is comes out of the box.
A lot of people underestimate the power of extension methods.
.nets metadata system is compiled time safe where Go is not and this has burnt me a lot.
I really like go for command line tools Small binary sizes I lovely.
go's standard library is significantly more hackable than .nets there have been a few things where I've ended up using Go because to do it in .net would require me maintaining a fork of the standard library.
Go makes composing data insanely simple. where in .NET you can have an equivalency but it will be done via interfaces.
Go's Dark Type System is both great and really annoying.
C#'s stricter type system when it comes to interfaces can be really handy as that can be used as metadata to change how data is processed and enforce compile time checks.
I'm going to talk about the second part first. .NET is often deployed with a JIT compiler runtime like Java. That means that hello-world scripts use a lot more memory than Go, but that difference becomes a lot smaller on real-world programs and .NET can use less memory than Go depending on what you're doing.
C#'s concurrency can also be more efficient than Go's since it's coroutine based async/await is a lightweight state machine rather than green threads. That's not to say Go is especially heavy or anything, but it is more overhead.
In terms of "efficient in syntax", Go has a lot fewer features. That's one of Go's biggest strengths and weaknesses.
Here's an example. With Go, you just have struct tags: strings of anything that can be attached to a field in a struct. Let's say you have a User struct and you want to make it so the JSON won't have the user's email in it. You do: Email string `json:"-"`. But what if you mess up and write "jsom" or `json:-` without the quotes? It's basically a meaningless string to the compiler. With C#/.NET, you have annotatons/attributes that are a part of the language and checked as types. You'd do: [JsonIgnore] public string Email. JsonIgnore is an attribute class so your IDE can help you.
There's a lot in Go where the answer kinda becomes "f-it, we'll just use strings instead of having something meaningful." Go talks about how important error handling is, but then you get an error from a function and it's basically just a string. If you're doing good error handling, you want to handle different errors differently, but that's not easy in Go. Frankly, it's not easy in most languages, but this is definitely an area where it felt like Go gave up on creating something good in order to get something out the door quickly enough that Google didn't cancel the project. In a language like Zig, errors are enums and you get exhaustiveness checking. With Go, someone could add another error state to something you're using and you'd never know - or they could change the phrasing of an existing error and you'd silently stop matching on it.
Go's lambda syntax is clunky. func(x MyType) string { x.ToString(); } vs (x) => x.ToString()
There's lots of stuff like that. I'm not arguing that Go is bad, but it has a ton less stuff and that's both a blessing and a curse.
It is universal interface to query diverse sources of data. Most often you use linq to query arrays or lists. This is implemented as a set of extension methods on IEnumerable. Implementations for other data sources exists, most prominently Entity Framework which translates linq queries to sql queries.
Modern C# is a much more powerful and expressive language than Go: iterators, collection expressions, records, async await, source generator, much more powerful generics, low level memory management features like Span<T> and ref struct, etc. It is closer to C++ with opt out GC/opt in AoT than it is the old Java comparison days.
Go's advantage is mostly the fast compile speed, goroutines, profiling, and a very opinionated formatter. Neither has an overwhelming advantage over the other though.
Well for me, that's like asking why don't you write everything in Erlang rather than C#.
Go is for me just a, hmm, concurrency programming language. Its popularity stems just from being pushed by Google, and the cosmetics of using braces. Despite what pundits say, it's completely and utterly not like C.
While C# is a successful and comfortable everything-language. It is blazing fast, has amazing tooling, you can get going to prototyping, then enterprise code, its serverside handling is excellent. It has many low level features and built-in data structures. It's I would say preferable for almost any kind of work except scripting. (I really don't like C# scripting.)
In addition to what the others have said, I also enjoy the extremely robust ringtone reflection system in .NET. I don't know of any other systems that provide more complete detail. Maybe Java, though it's been a while since I've used it. It makes reflection in other languages feel prehistoricaly primitive.
I wonder what this means for custom async implementations in the long run.
I was considering using my own AsyncBuilder to bring over a rudimentary "computation expression" capability. But that might be out of question now.
My first job out of college was at Microsoft, probably at the height of its powers. I was a developer on the .NET team and helped bring SQLCLR to life, the regrettable decision to let you run .NET code inside SQL Server. That job opened a lot of interesting doors for me. The weather sucked though.
To all the .NET outsiders, C# is indeed the .NET darling but you can also use F# if you feel like living in the future. Or VB if you feel nostalgic. So in .Net land, C# is not destiny. It is a choice
I feel like VB is a "better" Python for beginners and F# is a much better Python for data scientists.
The tooling ecosystem in Python is just so bad that I can barely stomach it after spending the last 20 years in .NET. At times, when supporting analysts at work wanting to write a little glue code, I've found their main complaint about C# is "curly braces" (which seems like a really odd thing to complain about, but ok) and I've found it easier to teach them a little VB than to figure out whatever the hell is going on with dependency and runtime version management in Python.
And then, for the folks who go deep on Python for ML tasks, I've mostly found them caring now about specific libraries than the language itself. I then show them how .NET has equivalent or better libraries available and that a lot of the code-golf stuff they do in Python has a much cleaner version in F#.
That’s always been my impression. Working with azure and mssql etc - great. Slightly off the enterprise app path - immediately dragons and lack of drivers, convenience and gpu, etc etc.
Ugh, I agree, Python tooling makes me want to pull my hair out every now and then. And then I weep for IronPython... it could have been a great blend, if it hadn't been abandoned. Though I can imagine that trying to "marry" Python to .NET became more and more of an uphill battle over the years. I think the last maintenance release was about two years ago.
It's "good" that we have a hashset with syntactically four entries and no error/warning about duplicate keys and it ends up with just two and that there's an entry "with(...)" that's not actually an entry. And that's even before we get into how something called a StringComparer is providing a hash function and how OrdinalIgnoreCase is an underdefined upper-casing system rather than a true case-folding one, even if those are historical.
Makes me confident in C# to see the language in the hands of talented and clearly passionate designers like you.
As for your DU proposal, between Union types and enums, I think you know that anyone who has used TS or Rust or F# or OCaml will have a strong preference for the Union based syntax.
C# is a really great language, and I hope I can keep using it. I like Microsoft’s ecosystem, but personally, I’d like to see its cross-platform support strengthened a bit more.
MAUI is still not great, is it? I remember Linux wasn't supported either. I tried MAUI on macOS, but honestly it's hard to consider that supported. Many people use WinForms and WPF, but these don't support cross-platform, and honestly WinUI 3 is also lacking. On the server side, TypeScript is actually better (both are Microsoft languages).
In fact, desktop apps are mostly used for factories or Windows-only programs, which is a bit disappointing. It's a very good language for making games and the most widely used language for factory programming, but whether to build a product with it is a bit ambiguous. I'd use it when making a program for a specific customer, but for a product I'd probably use TypeScript.
If anyone wants cross-platform desktop GUIs to actually happen (in the way that people mean when they talk about this stuff—i.e. neither Electron nor Qt), then they're going to have to implement Cocoa for Windows and Linux. There is no other way.
If you're limiting yourself to Microsoft's options then I'm going to have to say that's a you problem. Avalonia is pretty great. What do you use when you're building apps in other languages?
> On the server side, TypeScript is actually better
I like TypeScript but I'd never pick it over C# on the server side unless I had to do React SSR for some reason.
The issue is that it's not a hobby, but a delivery. For delivery, you have to check commercial compatibility and long-term maintainability, so I'm not sure.
Actually, I don't particularly like the XAML model itself, but the problem is the LTS policy and long-term viability. In delivery, technology choice is important. Because what can be maintained later is what matters.
I have learned TypeScript, but I don't think you're wrong. It's sufficiently understandable for personal development and hobbies, but from a delivery perspective, it could be considered a risky choice.
I often build ERPs too, and for delivery I usually use TypeScript with Electron.
C#'s biggest problem is mobile support. It's very strong on the game side, but weak on most phones. TypeScript, on the other hand, uses a bit more memory but is easier to wrap for mobile. You can just use Capacitor alongside Electron.
In my case, it's not a huge project—it's usually a small ERP dedicated to a local company in the region, so even if there are many employees, it's often only 100–200 people. At that scale, TypeScript holds up well enough. (For factory MES, there are tens of thousands of data points, but in that case it's usually fixed to C#.)
In this context, the mobile option in the ERP is good for charging customers, and it offers good extensibility as a reference option.
Unless WASM performance improves, there's still a bit of latency. On top of that, in my country there are few maintenance personnel.
I feel like they've done a lot to resolve a lot of the confusing versioning and runtime compatibility problems that drove away non-Windows developers from using .NET.
However they still suffer from an incredible branding problem.
It's really too bad because as a runtime it's fairly incredible, and F# in particular is fun to work with. They also have good cross-platform support and tooling, which they didn't have a number of years ago.
Combine that with the fact that nobody really writes code anyway thanks to AI, and this could be a slam dunk runtime for an AI-native development environment.
> ...this could be a slam dunk runtime for an AI-native development environment.
I wrote this in my other comment, but I really think that C# is an underappreciated language and tooling stack for AI.
- Roslyn analyzers (C# code analyzers written in C# that run on the C# compiler)
- Simple, stable tool chain
- Language has largely been stable and backwards compatible for the two and half decades of its existence
- Now have a very, very good type system that promotes good practices
- Very good corpus of docs
They have a section for breaking changes in .NET 11, containing a few dozen items. It starts with "If you're migrating an app to .NET 11", as if you'd ever have a choice. Microsoft forces you to upgrade every 2 years, and they ship breaking changes. It is completely unacceptable that these two coexist, and I'm confused why more people aren't angry about it.
Either you should not be forced to upgrade, or backward compatibility should be guaranteed -- not both. But Microsoft is happy to say "hey, we're going to completely abandon support for every version of .NET every 2 years, and if you can't keep up to date then everything that goes wrong is all your fault." Meanwhile, they have no problem breaking your code.
This essentially kills the possibility of just writing a bunch of small, useful libraries, and having them be useful forever. It is the antithesis of a healthy software ecosystem. Are you really expecting that every single open source library that is referenced by a reference of your references is going to be maintained in the face of breaking changes every 2 years? If not, then upgrading your version of .NET is just a gamble that things won't randomly break. But you must upgrade, or you're a bad developer who's leaving your apps vulnerable and every automated security checking system in the world will yell at you about how awful you are. You must upgrade everything you have ever written every two years, and hope that every library you've ever depended on does the same, or you are putting yourself at risk of some breaking change sneaking its way in, or leaving yourself vulnerable to a critical security issue that Microsoft refuses to back-apply.
I cannot understand why I'm the only one shouting from the rooftops about this. It's completely unacceptable to me.
Plenty of systems still use .NET 8/9 today. It's very likely that there are some websites that we use daily running on .NET 8 with ASP. Microsoft never barged in our server room and forced us to upgrade .NET, but YMMV.
I think a lot of this stems from the fact that we live in a "why we can't have nice things" world. As long as there are bad actors, there will need to be security updates. So if you don't update, and Microsoft no longer provides security updates for that version, you're in quite the bind.
If I wrote a useful library a few years back and it targeted net472 and net6.0 then… it just keeps targeting those? I can still use it from a net48 and net10.0 app just fine?
No, you cannot use it "just fine". There could be any number of breaking changes to your library between .NET 6 and .NET 10, and you're not guaranteed that a build will catch them. Have you painstakingly reviewed every line of code against every breaking change that Microsoft introduces with every new runtime?
I personally saw, this week, an internal library that worked in .NET 8 but threw runtime errors when built in .NET 10. Previously working code can just break in subtle ways, and it could be anywhere, in any library in your chain of dependencies. In an ecosystem where you are forced to upgrade every 2 years, this is not OK. This is not a feasible way to build an ecosystem of interconnected libraries, and I believe the only reason it's not more painfully obvious is that .NET Core just isn't that old yet. This is going to hurt more and more as time goes on.
There's a reason Microsoft was obsessed with backward compatibility between 1980 and 2016. This never happened with .NET Framework. I don't know why Microsoft suddenly decided backward compatibility no longer matters, but it's a big mistake.
In my experience, C# and .NET are both "underappreciated" in a large part based on the legacy.
Some folks out there still think .NET is Windows only. Some folks out there haven't looked at C# since the .NET Framework days. Some folks out there are still salty about some of Microsoft's decision around OSS (debugger, hot reload fiasco, etc.)
But day to day working with C# and .NET for backends? It's really fantastic. AI seems to like it a lot and a lot of tools for agents to go HAM. Roslyn analyzer, source generators, workable hot reload (for a compiled language), tons of really good docs from Microsoft, generally low variance and high consistency from codebase to codebase (e.g. encountering Drizzle vs Prisma vs TypeORM vs Kysely vs ... in Node land).
My previous team switched from TS to C# and F#[0][1] and it was a blessing; a lot of the platform ops have been simplified. It's just one very solid tool chain and packages are generally stable (a lot less effort on maintaining platform config/tooling and more focus on just building). First party packages get consistent security updates and C# doesn't have nearly the same level of exposure to transitive packages that one encounters with JS/TS ecosystem.
C# feels like a really solid choice in the age of AI coding because of it's general stability, strong static analyzer, insistence on types, and really good tooling. Not right for all use cases (e.g. CLI, serverless functions), but fantastic for backend APIs.
If you're looking to do AI with/on C#, I wrote about some of my experience with making codebases successful for AI[2].
[0] https://engineering.usemotion.com/moving-off-of-typescript-e...
[1] https://typescript-is-like-csharp.chrlschn.dev/
[2] https://chrlschn.dev/blog/2026/08/the-unexpected-ai-stack-cs...
Being a Microsoft product is such a heavy baggage. Last time I checked the official debugger was still not open sourced and that alone was enough to make many people think twice. The fact that MSFT chose golang instead of C# for Typescript compiler, the state of Windows native GUI app dev... all erode C#'s reputation.
It's a good language though. The latest trend of hating OOP is unjustified/overreaction. I do write Rust too and I am not really convinced that traits are better than shallow inheritance + interface-oriented classes.
It is weird now that you mention it. I worked in C# and .NET for a long time. In theory for 20 years. In practice I wasn't writing much C# after 2019 even though the team I led still used it.
I guess I... just got bored of it, and fed up of Microsoft. I came to hate Windows and having to work on Windows so much that it sort of tainted C# for me. I'm not saying I'd never use it again but, whilst it will run cross-platform nowadays, so will loads of other languages and frameworks, so it's never at the top of the list when it comes to choosing something for a new project.
As for TypeScript? Absolutely loathe it. I'll use it professionally when I have to, but I'd never use it for one of my own projects. It's just so ugly I don't even like looking at it. I get why people like it but, for me, it's not enough. If I'm staring at something all day then, I'm sorry, it can't be ugly.
But, strangely, I still use and enjoy VS Code from Microsoft, and have no plans to switch to anything else.
(I also still have an Office subscription - or whatever it is Microsoft are calling that suite nowadays, I don't care - because I still use both Excel and Word fairly frequently. I could probably get by without, but I actually quite like Office - even on the Mac.)
The main things I don't like about TS are the volatile tooling, bad habits from other devs (not declaring types), compromised ORMs, require lots of package hunting, and module system.
Language is fine, tho.
Ironic that you quit using it because of Windows right around the time you no longer needed to be on Windows to use it.
I needed to be on Windows for work though and, sadly, often still do, so the association is strong.
The thing is, now most things I do target some flavour of Linux - and although I could write C# and target Linux with absolutely no problem - there are a lot of other options that C# has to complete with. Granted those options would also, at least in theory, have been viable options on Windows but it definitely would have felt a bit like paddling against the flow to host Ruby on Rails or Python or whatever on Windows.
And if I want to target command line or desktop with something cross-platform I'm going to compile to a native binary that requires minimal dependencies, so Rust feels like a more natural fit than C#, especially with Microsoft's desktop UI story being such a dog's breakfast (and even more-so when you account for cross-platform).
Again, in a commercial environment, I could imagine using Electron for that sort of thing because that's what most companies do (but because everything I do nowadays is backend, web, or systems integration, I haven't written a desktop app in a commercial context in 13 years, and I haven't written a command line app in that context since just before the pandemic). However, for my own projects I want to hew as closely as possible to my own principles and ideals and, for them, it'll be a cold day in hell before I ship a desktop or command line app that needs some giant runtime like Electron as a dependency.
Again, the point is that C#, whilst in many ways a great language, doesn't come out as the frontrunner.
Did anyone ever explain why C# was not considered in the first place ?
Yes, Anders Hejlsberg did various times in podcasts when they announced the move go. It has nothing to do with C# not being capable enough, but rather that a line-by-line port from Typescript to Go would be easier. I think IIRC several time that had LLMs be more advanced as they are today, that choice would have been a different one.
Yes, it's because the structure of the existing codebase for the typescript compiler matched go better. (There's a blogpost about it somewhere)
edit: I think it was considered, but go was a better fit
What does "structure" even mean ? How or why are models so much easier in Go compared to other languages ?
Go models aren't easier, but the existing TypeScript codebase leaned heavily toward structural types, which made it a better match for Go and at least initially made porting easier for them.
https://github.com/microsoft/typescript-go/discussions/411
TypeScript has fewer competitors for the frontend than C# does for the backend
https://gist.github.com/matthiasak/c3c9c40d0f98ca91def1
JavaScript is one of the most common compile targets. "Who's the better JavaScript" was the most contested field a decade ago. These are not just toy languages - Facebook has Flow and Google has Dart. Both companies are far more web-adjacent than Microsoft. Yet Typescript won. It won so hard that we feel it has no competitors today.
Speaking of PostgreSQL and Microsoft, I was at at a presentation earlier this year and they are developing an underlying storage engine for it based on Azure Blob storage to get “instant” WAL replay across many replicas at once.
It was pretty impressive work to scale PostgreSQL.
Sounds very similar to what Amazon has had with aurora postgres for many years now. Very effective but not novel from ms.
Well, there's a lot of sides of it.
On the historical (And semi-current-ish) downside we had/have;
- lots of 'flash in the pan' stuff between 2008-2012ish (Silverlight comes to mind here)
- Anemic Entity Framework in pre-core, and the weird stuff EF Core had going on early in it's life.
- That constant worry as to whether you need to add .ConfigureAwait(false) to every async call in a library you want to open source.
- The thing where trying to do 'traits' in NetCore (Now that you can mostly mimic the behavior) has had some side-effects on performance, mostly resolved but still something you have to consider as far as impact. (To be clear tho, thank god, needed that...)
- WTF was that UWP workflow?
- Why can't we get a proper designer? I don't think it would be that hard especially given I can do nice things around workflow designers today. Speaking of workflows...
- The complete (what I probably guess was MS groups eating their own tail) thing where Workflow Foundation was almost truly productive, SSMS had a thing based on it that was surprisingly productive, and it has been murdered instead of getting the TLC it needed to really disrupt. This one is extra loud because it is so similar to the 'low code' platforms so many orgs have sank money into.
- Less impactful as far as people eating the lunch microsoft left on the table, but WCF.
The upshot is;
- If you're willing to do some looking there are great options out there. lazy example, linq2db is basically a DSL that is close enough to EFCore as far as 'how you compose' that you can teach it to a group of WITCH devs in 30 minutes or less.
- It really is easy to do all sorts of stuff. It's just productive.
I've narrowly focused on .NET and C# for HTTP backends. In that capacity, it's been very stable and gotten better with each release.
EF Core now is very, very good with only one minor gap with respect to migration merging (long migration history causes build times to explode).
Every company I've ever worked for used C# to write business apps. The software that handles a huge fraction of all closed captioning of telephone calls is C#.
Also they dropped any pretence of doing any long term support, which is going to create all sort of interesting CVE over the long run given that it is mostly used now for backend.
And I am still not over the many breaking changes when they dumped full framework. Have to support a large code base, I think only now with AI will it be economical to redevelop it for .net core.
The decision to use Go for TS7 compiler is even more baffling considering Go's low level memory management functionalities is nowhere near the level of C#'s Span<T> etc, especially since TS and C# are invented by the same person.
I got so worked up by it I built my own TyepScript compiler with my clankers in C# to prove that it can be done, my revival of the Common Language Runtime concept, project Copeland:
https://github.com/yuechen-li-dev/copeland
Currently, Copeland compiles a dialect of TypeScript to JavaScript, C#, WASM via Blazor WebAssembly, and NativeAOT, runs in csproj, and uses xUnit.NET for testing framework.
(The other reason is that Microsoft still refuses to do the obvious thing in .NET 11 and take the best part of Rust and let enums be `match`ed, despite the fact that both TypeScript and C# have weak enums historically, so I added payload enums to my TypeScript dialect because Microsoft won't.)
https://learn.microsoft.com/en-us/dotnet/csharp/language-ref...
Pattern matching is in C# unless I misunderstood you.
You are right about the pattern matching, so it's on me that I didn't write clearly enough, what I meant is that I wish C# had Rust's compiler enforced exhaustive pattern matching over enums and the ability to carry data payloads:
https://doc.rust-lang.org/book/ch06-01-defining-an-enum.html
Instead of .NET 11's more awkward (imo) `union` form.
https://learn.microsoft.com/en-us/dotnet/csharp/language-ref...
There's an open-source debugger by Samsung, compatible with GDB/MI and the DAP protocols: https://github.com/Samsung/netcoredbg
I love C# and F#, but every .NET shop I worked for was full of MS cool aid drinkers and enterprisey nonsense. I don’t miss the culture at all. The languages are excellent, though.
This. It's so nice to work on C# stuff on your own without any of that baggage.
In my experience this is changing a lot and very quickly
Exactly this. The workaround is to explicitly hire developers from other languages whose idiomatic use matches your style.
Going to r/csharp over on Reddit is such a weird experience coming from any other background, save perhaps Java. A lot of people just yolo'ing with MockFactoryAbstractFactoryFactory abominations and injecting dependencies into your dependencies so you can inject your dependencies.
A lot of people also seem to have bought into the "Clean Code" dogma popular in the 00s, completely still living in a world where RAM is cheap and JITs are magical.
Add to this the absolutely miserable state of Microsoft's official .NET documentation, and it's a bizarre experience. Everything is "documented" - with things like "Foo(): Constructs a new instance of the Foo class", in case you were wondering what a constructor is...
Context: I'm coming from a background of C++ and Rust. Especially the latter leaves .NET's documentation in the dust, but that doesn't mean the CLR isn't a piece of extremely solid engineering. I'm continually impressed by the .NET JIT and GC, for example, and when you dive into the poorly documented details of the standard library (it's open source these days), you'll see that the quality is very high almost every time.
So yeah, I agree that .NET and C# has everything it needs to be really great in the right hands, but many hands aren't super right.
Is there any book that you'd recommend for modern, sane C# development? I've inherited a .net project and coming from Rails, a lot of things seem plain weird.
I don't know man... Rails seems fairly unhinged to me as well, but it's been a few years since I touched it.
My recommendation is always to understand the technology in depth, then make decisions based on what makes engineering sense. Understand the tools first. For me, that's a hands-on process, but others learn better from books. A lot of programming books seem a bit ideological from a methodological point of view, often written by people who thought they found the magic bullet (see the "Clean Code" fiasco) with perhaps limited time in the trenches.
Agree, in Java world, most people have migrated from those design patterns, while the dotnet world still seems to be occupied with them.
I've been writing C# for literally 25 years.
I still choose .NET as the go-to whenever I can. On Windows or Linux.
I think the baggage view was valid for a while - but now we have a new generation of developers who only know about .Net Core and may not associate the Framework years in quite the same way.
I've been writing .NET code for 25 years too. I've tried a lot of other languages and platforms. Every time I've come back to .NET. All my web apps run on bare metal Kestrel under Linux which has come such a long way. I write all my Windows desktop apps in .NET too. Very clean code, easy to read, easy to write.
the APIs/primitives are so good too
I wrote C# non-professionally long ago and loved it.
I recently tried to come back and the problem isn't the language. It's getting into it. Try to click "new project" and you're met with dozens of templates, and a lot of them sound identical to the untrained observer. Which web project should you start with? Which GUI project?
I pushed through it and made the wrong choice a few times for each of those. Apparently there are stable old one, and rapidly changing and underdeveloped new ones, and no way to tell them apart other than knowing the magic incantation.
Microsoft would do themselves a major favor if they cleaned that up and made things clearer, including definite guidance on why you should pick one template over another.
Instead, they treat them all as if they're perfect and newcomers have to just guess.
And no, web research doesn't help. There are tons of people who love each of them, and only a few people willing to write well-reasoned articles based on their experience, helping someone pick the best ones to start with.
I know this is not really the case for many others, but for me it's because there's a lot of set up; that's usually done by IDEs, but I'm unlikely to get an IDE before I've kicked the tires a bit. Go is run "go mod init", or hand write one, or even skip that step to write a single file thing. JavaScript is similar, you can start with one file, or start with a relatively simple package.json. Python / ruby / Perl / PHP is just the one file.
As far as I know, C# and friends still require setting up a project file, and there isn't a tutorial that has one you can easily copy.
Maybe this is why I also can't get into Android development.
(It also doesn't help that my Linux distro doesn't ship the toolchain because it can't be bootstrapped from text files, as far as I understand it)
Does `dotnet new <template>` not count?
You could also `dotnet run script.cs` to run a single file without setting up project.
This used to be true but is very much false now! The CLI can create any new project, even complicated ones. You can create your own project templates if you want. You can even start a solution and project files in an editor. It’s all just xml!
A lot of this changed with .NET Core and later numbered .NET releases (currently 11 as you see from this thread :)
The tool chain has matured significantly and stabilized and it's quite easy to use. IMO, much easier than building backends with TS (the madness that is tsconfig.json) and getting configuration correct with TS tool chain (some easier than others).This should do it:
dotnet new console
dotnet run
You are exactly proving the parent comment right!
I recently started a new project on .NET, mainly for its batteries included approach, seemingly a bit more serious ecosystem and the tooling AI agents can leverage during development and ops. Really happy so far, but too early and too small to have meaningful conclusions yet.
I'm especially interested in exploring F# as a concise, non ambiguous way to communicate definitions between humans and agents, somewhere between writing code like the olden days of yesteryear and describing things in English prose.
If you're going heavy into AI coding with C# (like approaching 100% AI code), give my 5 part writeup a read. Battle tested configuration with a fast moving team that was pretty much 100% AI code with good code quality. The configuration for `csharprepl` in particular is a special kind of unlock; having the agent be able to reach into the running DI container, wrap/replace functions while app is running just unlocks a lot of capabilities for the agent. It's the difference between an ultrasound and reaching in and manipulating the organ.
Oh, thank you, this looks really helpful at this stage!
Yes, the plan is essentially 100% AI code with a small team of programmers and domain experts from other technical backgrounds. But still depends on a few things on the business end before I can really kick it off. More of an exploration until then.
I'm just starting to get a handle on how to use agents, can you give a short description of how you get your agents to use csharprepl
I have more detail written up here: https://chrlschn.dev/blog/2026/08/the-unexpected-ai-stack-cs...
But the gist of it is that I wire it up through Aspire (don't need to, but makes it nice) to set the environment variable on my main app host. Then the agent can run `csharprepl` commands directly via CLI and read/mutate the running application state directly via the service provider (DI container)
I have a small set of utility functions to access the DI container with common patterns like creating a scope for scoped services: https://github.com/zeeq-ai/zeeq-app/blob/main/src/backend/Ze... (optional, but useful)
And a skill: https://github.com/zeeq-ai/zeeq-app/blob/main/.agents/skills... (also optional, but cuts down on agents relearning the #wrap and #replace behavior)
I recommend you "train" the skill like I do in my the blog post. Basically ask the agent to get an instance of an object from DI like a repository class or your DbContext and have it perform some operations. Have it wrap a function and add logging. Have it replace a function. Then once it learns how to do this, have it write the skill bespoke to your project.
I strongly recommend that you try it "by hand" one time via blog of CSharpRepl author: https://fuqua.io/blog/2026/06/injecting-a-csharp-repl-into-a... If you try it manually one time, it clicks what the agent is doing and why it is so powerful (because the agent accesses and modifies the running application state without a compile).
That looks really awesome, thanks!
Yeah. I fell in love with it about 15 years ago during undergrad at an internship. Built a few of my senior projects in it, and have made a career out of being good at it. It's been such a pleasure to see it turn into a general purpose non-MS locked language that i can run anywhere and do anything with, and it only gets better every year.
Thanks for the links. Your agent repl thing looks cool as hell.
Same experience here. My side project's backend is .NET 10 on Linux, in Docker on a single Hetzner box. It polls the now-playing line of ~58k internet radio stations every 20-60s and serves it as an API (tuneping.com). It's one process for all the background work (sync, polling, matching, webhooks), one for the API, and one Postgres. The thing I'd point at is the "low variance" you mention: hosted services, config and logging all work the same way, so there's very little glue code I had to invent.
I generally agree but so far every place I've been that had a sizable .NET codebase were stuck on Framework and so in reality it was very much Windows-only.
Modern .NET is great though.
It's certainly not a big topic of discussion online but it's powered all of the healthcare infra my career has been around. There's a lot of "dark matter" code written in C#.
I pretty much exclusively use .NET with AI for backend stuff and run it all in linux containers. It's really good. I generally try to stick to stock standard .NET with only a few popular packages where it makes sense. You don't actually need too many extra things as there's quite a lot built in. Even for CLI its ok, though it won't be the smallest binary.
The thing I do not like about modern .NET is that Microsoft completely switched to AI-generated garbage as their documentation. This may be ok if you only interact with it though agents but painful to read otherwise.
I'm curious why you would consider C# not right for a CLI? There are some jagged edges with xplat support (e.g. System.Environment.SetEnvironmentVariable had/has issues on Linux, haven't checked recently), but IMHO its pretty solid for composing CLIs.
It's "OK", but the benefits of Rust or Go are undeniable here: faster startup, smaller package, likely lower memory footprint by default.
You can get C# to match Rust or Go, but those would require going off the beaten path and you might as well use Rust or Go.
I think C# CLI for internal tooling that is only used by an existing C# codebase makes perfect sense. As a standalone tool that's being shipped for multiple platforms, it would be better to use Rust or Go since those will give smaller native binaries and default better performance profiles in speed and memory usage.
I think backend web APIs are where .NET shines.
> Some folks out there are still salty about some of Microsoft's decision around OSS (debugger, hot reload fiasco, etc.)
Have these decisions changed? Is everything now open source?
No, although LLMs have reduced the amount of time you use those tools.
Wiring CSharpRepl to the running app and handing that to an agent is :chefs_kiss:
The fact that when most devs look at C#, they can only see the silhouette of Microsoft is kind of making me mad sometimes, but I think it is understandable.
The corporate history of C# is undeniably strong, and even till this day in modern C#, they still have a lot of legacy components in it, like AppDomain, APM and MarshalByRefObject.
It is also unfortunately that C# has largely lost in the AI race, that it failed to gain tractions from AI developers despite heavy investments from Microsoft. Early AI stuff like Semantic Kernel actually included C# as its primary languages besides from Python. Nowadays it is not mentioned so much and the default is just Python but perhaps it really isn't a fair comparison due to the gravity of Python in AI as a whole.
Yet for another fact that even within Microsoft, Rust wins over C# for agentic development, is a clear indication that C# has lost a meaningful place in software development as its former parent started to berate it as well. TypeScript is rewritten in Go rather than C#, and consider the fact that Anders Hejlsberg made both C# and TypeScript...
Sure, maybe Bing and a large part of Azure and Xbox services still have heavily relying on it, probably because of ASP.NET Core and Orleans, but outside of the two Microsoft greenhouses, it is a free for all battle royale where C# don't even have a chance of being picked at due to being too weak.
I even tried to contribute a C# SDK to the Restate project, but the maintainers actively rejected it due to them not being familiar and refused the handover. They said they will consider if there are commercial fundings backing it, then they would starting to consider (that's even with money attached they would still think about it). This also shows how unpopular C#/dotnet is among open source development and failed to build hype. The same Restate project even have a Java SDK for it, so it is not like they can't handle C#, they just didn't like to.
The last few thing C# is good at is just Unity I'm afraid, and that is also dying too as Unity is also not doing so well lately.
Rust and C# serve different purposes. For things like CLI tooling and the Copilot SDK (supports Go, Python, JS/TS, Java, .NET), Rust makes total sense.
For web API backends and MCP servers, I firmly believe C# is the better choice.
For AI use cases (as in make calls to LLMs), there's not much difference, IMO from Python and C# and TypeScript. At the end of the day, those are all just REST API calls and SDKs are mostly wrappers around it.
For LoB apps where the AI/LLM is just one small leg of the actual flow, C# is still a winner, IMO, because of how good the overall stack is (e.g. EF Core). Orleans (actor model framework) is also super interesting and I'm exploring some memory/communication system based around it.
TypeScript was "ported" to Go because it matched the closest & allowed easy line for line swapping. C#, F# or Rust would have required a re-write with architecture changes instead.
https://devblogs.microsoft.com/typescript/typescript-native-...
Semantic Kernel isn't getting updates because they've replaced it with Agent Framework.
It’s also awesome for SREs / SysOps / Whatever We Call Ourselves Today:
Opinionated in a good way, which means almost all C# apps have the same parameterisation with environment variables, command line, and config files. It’s all very systematic, consistent, but flexible and pluggable.
“Node has that too!” you’re about to say. Lol, no. It has bits and pieces the devs have to wire themselves, which means a fleet of vaguely related apps made by independent teams will have wildly different config styles. Good luck consistently making all of them pull API client certs from KMS or Key Vault, or whatever!
OpenTelemetry is now baked in to the core components and rapidly spreading through other libraries.
Excellent “pervasive” system APIs for spans, traces, metrics, and logs means that the sources are omnipresent and very rich, going back decades. Even very old third party libraries are often a breeze to instrument.
Debug snapshots from production crashes on App Service feels like cheating.
Built in container build outputs with no extra tooling.
Sane defaults for its package manager means far fewer nasty security surprises.
Far more first-party “coverage” of essential capabilities in the optional Microsoft packages so you don’t have to resort to NPM where your security-critical code is written by Sergei and support is “LOL Wut!?”
A GC that automatically responds to container limits instead of the host limits.
It uses thread pools for async tasks in most configurations so whole categories of production-time issues that occur with single threaded runtimes just doesn’t happen.
Etc, etc…
How does AI do with F#?
AIs write pretty good F#, but there simply isn't very much of a reason to use F# over C# since Microsoft basically uses F# as an experimental test bed to test .NET features before bringing them into C#. Plus, annoyingly, F# doesn't interop well with existing C# projects which there is even less of a reason for existing .NET shops to adopt F#.
I also agree with this. When we were internally finalizing on C#/F#, I showed the CTO that most of what he liked about F# was already possible in C# (though requiring third party libs like OneOf or Dunet) with the benefit that C# syntax is more familiar for most folks. With 15, I think they are even closer now.
One of my favorite things about F# is what some would consider a deficiency: that within a project, files must be ordered and you may only refer to a symbol defined earlier, with the sole exception of types which are explicitly declared together in a single statement (`type X = ... and Y = ...`).
This in contrast to C# where, within one .csproj, there is no "order" and all classes and methods are able to mutually reference one another.
The F# way creates an annoying housekeeping step of maintaining file order, and would be a nightmare to do with manually edited .fsproj files (I have only ever written F# with an IDE), but IMO, it is worth it. On the reading side, it makes it easier to understand how a project you've never read before is structured, since you know the order of the F# source files also must be the bottom-to-top layering of the code. On the writing side, it sometimes causes one to rethink the architecture and simplify it to minimize the number of mutual declarations.
The advantage of F# over C# is defaults and baggage. The FP story in F# is great. In C# it’s possible.
That's a good way to put it. C# can be made closer to F#, but it has to be done so explicitly.
That might be true. But I still think code and ideas are more clearly expressed in a higher level language like F# still. Even though C# has adopted a lot of features from F# over the years, F# is still a lot cleaner to read and write with its excellent type inference and super minimalist syntax.
Seems very good at it; for a while in my F# project I didn't know how to tell claude code on the web how to install it, but claude still made code that worked fine - its COT said, paraphrasing: "no F# compiler here so I will check this code by inspection". Later I learned how to make F# available there - in claude.md tell it to install the ubuntu dotnet runtime via apt, which includes F#. May not be needed anymore though.
Motion shifted to C# around August of last year. Part of the reason, I suspect, is that the CTO (Hi Chander!) is an OCAML fanboy and F# was the most practical platform alternative.
We shifted all new product backend build to C# (because at this time last year, we were still "AI assisted" and deemed F# too foreign for most devs to read well), but all internal tooling was built on F#.
By maybe May of this year? We had multiple F# backend codebases, fully AI generated. Only one person on the entire team had worked with F# by hand. Several things work in F#'s favor even though it has a lot less representation in training: 1) the people that write F# really love F# so the code out in the wild is going to be better quality (hunch), 2) F# is a bit more terse/dense than C# and less noise in context, 3) F# benefits from the same tooling stack, 4) F# type system is still a bit more expressive than C#.
I know C# deeply so I know what good looks like and I know how to get agents to write good C#. F# I'm less familiar with, but we had great success with it.
when I realised I didn't have time to actually finish my own programming language (there was a big change needed in the type checker and it would have taken months with how much I can use a computer atm), I let claude something like 98% of the work, and it worked very well. The code comments are awful, but the code is ok. recently i have just let it have a go, and the code has grown more than I care for, but generally it has been doing well: https://github.com/bjoli/Bjolang
I've used F# on several projects, and right now we're building all our infra with it. AI does very well it these days.
AI isnt quite as creative and clever coders as humans though, but thats true for any language you direct it at, but its a bit more obvious for higher level languages like F#.
Built the massive enterprise backend of a super app fully in F#. From my experience, AI was able to write idiomatic F# code since Opus 4.8 (all models started doing very decent things around that period).
It's kind of bewildering for me that, despite all similarities between C# and TS (including their actual design architect), only one has received so much negativity.
Maybe MS can work out a longish-term roadmap, where in a certain future version C# and TS gradually "morph" into being the same thing (e.g. Typescript 10 == C# 20 or so)
I would love a "T#" which is a trimmed down, modern C# that converges with TS.
I suppose it would look a bit like F# (LMAO)
I think C# would be a great choice for both CLI apps and server less functions.
Other than that I totally agree with everything you said.
.NET has a pretty good AOT compiler since .NET 8
https://learn.microsoft.com/en-us/dotnet/core/deploying/nati...
I write all my CLI apps in C#, but I hear you on the startup time. I guess it depends what you are working on whether that little hiccup is a problem. I've never once noticed it with anything I've written.
I’d be curious whether a CLI app written in C# would compare to one in Python for startup time. Python isn’t exactly a speed demon, but I use it constantly.
I may have to play with F# for that.
I think by CLI apps they mean something more like Claude Code than ripgrep.
> Some folks out there still think .NET is Windows only.
Wait until you meet devs who don’t understand that other OS are a thing!
some folks here sound a little desperate
C# was my 2nd programming language after VB6. I remember in the early 2000s when I was 12, using my best friends computer to try and download VB .NET (he had faster internet) just because it was free, then being extremely disappointed when I couldnt just run my VB6 projects I wanted to run on it... Some years later, during 2007 ~ 2009 I tried out C# and that was life changing.
I wound up going to college after working retail at Disney, then after college, my first job was supposed to be C# but they didn't have the project approved, so I got my first exposure to Python - which I loved, then JavaScript - which I hated but have a love-hate with to this day, and finally C#, but it wasn't .NET 3.5 no, they started using .NET Core, which was... something else, lots of teeth pulling during the original .NET Core days.
So many wonderful projects made with .NET my only complaint is I wish Microsoft would make a cross-platform GUI stack they'd actually stick to, and make it work on Linux too. ;)
I know there's alternatives but nothing beats telling your coworkers "lets just use the built-in" when choosing a GUI stack.
We abandoned .Net years ago before our third major product rewrite prompted by withdrawal of various bits of tech they said was the next greatest thing. AppFabric, WWF, WCF and Silverlight hit us hard.
Once burned, twice shy.
That's fair enough, but the dust has largely settled on dotnet core. The death of Silverlight was long ago. I think the only thing at risk of abandonment is the native UI situation, which is a mess.
Hard to say what's native in Windows anymore. I imagine Win32 APIs and more recently Windows App SDK?
The most supported UI frameworks from MS for Windows are WPF and WinUI 3. UWP is dead and WinForms (even though closest to something native to Windows) is probably going to end up in the land of forgotten (sorry nostalgic Windows devs, you have to face it eventually).
A "mess" is an understatement when the people who themselves develop Windows desktop apps have trouble explaining the landscape.
> Win32 APIs
Everything boils back down to Win32, including WPF, WinForms, WinUI2, and WinUI3.
Some of the more modern stuff is in a separate WinRT category that is a pain to use. E.g. requires "identity".
https://learn.microsoft.com/en-us/windows/apps/desktop/moder...
So if I decide to code a windows app just using C Win32 api, will it outlive the current generations and be guaranteed to run fine(compatible) on future versions, while the modern alternatives will be stuck in a constant cycle of deprecation?
That seems to be the history of every other toolkit Microsoft has released, yes. What a waste of time writing anything in MFC was.
The most supported UI framework from Microsoft for Windows is .NET MAUI, released in 2022.
It's not quite that settled yet. Lots of third party libraries are getting abandoned pretty quickly. We are also stuck in purgatory due to a VSTO add-in which we can't rewrite in Office.js because it's nowhere near as powerful and there is no usable .Net Core integration.
I used to be in the anti .NET camp but for Enterprise applications .NET has been delightful and upgrading between versions has been trivial since .NET 6.
> upgrading between versions has been trivial since .NET 6
I don't understand this. I love C# as a language, but the idea that everyone is forced to upgrade every single library every 2 years is beyond unreasonable. I've recently run into a situation where some configuration behavior deep in ASP.NET just randomly changed between .NET 8 and .NET 10, and broke things. A runtime exception, not a compile-time one. What else is subtly broken in a DLL referenced by a reference of a reference? You may never know.
It seems like every software engineer has forgotten that code is ultimately math. All of the greats understood this, from the times of Alan Turing through the 1990s. Then all of a sudden it became some sort of a toy, a game for 20-year-olds to one-up each other, to move fast and break things. Somehow, as software development matured, it became less of a serious discipline. Now, we have an entire ecosystem where it's just accepted that any line of code you've ever written may just randomly break every 2 years. You can no longer just write a small C# library and expect to to continue to work forever. It's completely unacceptable in my view.
It sounds like .NET Framework is for you. Support until sometime after 2030 (or more likely "forever", because it'll always be shipped with windows server and inherit its support lifecycle).
That said, I'd second the assertion that upgrades after .net6 are, for the vast majority of applications, trivial.
> the idea that everyone is forced to upgrade every single library every 2 years is beyond unreasonable
That's overstating the case. A library that targets dotnet 6.0 will often continue to restore and run in a dotnet 8.0 application. The notable exceptions are framework-aligned packages like Microsoft.Extensions. Most of the time you just need to bump the target framework in the props file, check it builds, run unit tests, and upgrade a few library packages that might need it.
We update all our dependencies weekly. It's mostly automated. This is the way it should be. It's not particularly hard.
We've always stuck to the server-side web technologies: WebForms (now very much obsolete), MVC, Web API + Angular, Blazor.
> Silverlight hit us hard.
So about that... when I started using .NET Core, it was bleeding edge and shiny, but it was to rewrite a Silverlight application, the only way to run it was Firefox ESR 54 (I think it was 54?) we thought "oh this is easy" only to find out that it was a colossal nightmare, EF didn't support some things, including Shapefiles, we had to force the support, get back a WKT from SQL Server, and send one back when we did updates, basically Entity Framework was mostly useless.
I also reverse scaffolded the existing database into EF compatible classes, so that saved us a ton of effort, the original idea was to be backwards compatible.
Best part? It never got used, when we found out they weren't using the project, my boss said, screw it, lets remake it in Python I know we can build it up quicker, and sure enough, it was done quicker than the C# rewrite, fastest rewrite I ever did, because he figured out the key things they actually needed, and trimmed the requirements to just those key things.
Sometimes rewrites don't need to be 1:1 especially if it's an internal only tool.
Anyway, lots of blood, sweat, tears, stress, etc from me as it was my first job in tech.
> various bits of tech they said was the next greatest thing. AppFabric, WWF, WCF and Silverlight
Always just only stick to the actual framework itself and never rely on these offerings from Microsoft and you'll have a fine time.
Never chase the shiniest toy at work! Give it a year or two to settle down first.
I think you underestimate how long we left it to start with.
> Silverlight.
Boy do I have good news for you (I am half kidding). OpenSilver project lets you compile your Silverlight code to Web Assembly or iOS/Android/Windows/Mac.
You can dust off your code now.
https://github.com/OpenSilver/OpenSilver
WCF, Silverlight... They were terrible. New .net stack is much better.
WCF had some good ideas and if you followed its best practices migrating to other networking stacks should have been straightforward. Delete your complex Bindings XML configs and just write implementations of your Contract interfaces and figure out where you need to dependency inject the new implementations. If you were deep enough into WCF you might have even had mock implementations you could bootstrap from.
That concept of "define an RPC interface and let the platform fill it" was ancient before WCF and continues to this day (I've got a bunch of tabs open for Cloudflare Durable Objects right now). The legacy problem with WCF was not but long term backward compatibility with the Bindings abstractions and XML config junk and SOAP Stack RPC. Without WCF native in the box in .NET 5+ it is harder (but not impossible) to build SOAP RPC monoliths. But so few businesses actually need SOAP and can't just switch to something more RESTful.
Silverlight was maybe ahead of its time given the growing popularity of Blazor WASM.
>I wish Microsoft would make a cross-platform GUI stack
I know this may as well be beseeched of the King of Nod, but if they just made WPF cross platform I'd be delighted beyond words.
I'd be okay with this, I'd also be okay with MAUI (which... is a whole behemoth of how to make UIs), but anything, hell I would take them porting WinForms somehow into a cross-platform .NET thing that runs everywhere (Mono used to!), I just want anything at this point.
XAML was supposed to become an open spec, not sure why they have so many teams making so many different ways to make UIs. Then there's those "Windows 8" apps, or the "Universal Windows Apps" which are poorly named, you cannot run any of them on previous versions of Windows, if anything Win32 apps are universal apps.
Checkout AvaloniaUI
Isn't this exactly the point of Avalonia?
Yes and Avalonia was created by not-Microsoft as Microsoft has repeatedly proven itself incapable of doing it.
I thought that's the point of MAUI, though I will say I never heard of anyone actually using it.
VB6 clone written in Rust:
https://github.com/iBobX/RapidR
Not compatible, but you can use it in a cross-platform way.
> I wish Microsoft would make a cross-platform GUI stack they'd actually stick to
Sinofsky left a lasting damage. The way things go with LLMs, it might be now pure app-specific Rust instead of any common frameworks....
https://learn.microsoft.com/en-us/dotnet/core/compatibility/...
Be aware
Wow, what a vicious footgun. I do a lot of work related to pricing, invoicing, exchange rates etc... where decimals do matter, so this kind of stresses me out. Thanks for the heads up.
You shouldn’t cast from double to decimal anyway. Just use decimal throughout.
Yeah, while I do agree this change in behavior could be a major footgun for a few people, the vast majority of use cases for decimal should be unaffected. Decimal is usually used for business applications to the exclusion of double/float, not alongside them (the only doubles I've seen in my company's applications are just storing Lat/Long values for addresses, everything else uses decimal values).
Hell, on the rare occasion I've needed to use a decimal literal in code, I just use a decimal literal: don't do (decimal)0.1, just use 0.1m.
Exactly. If you want an actual decimal literal you should always write it as 1.2345m rather than converting it with the (decimal) prefix. The compiler refuses to accept a plain 1.2345 assigned to a decimal type so it’s not such a footgun as it may appear to some; though it might offer a better DX if the type of the literal was inferred from the thing you are trying to assign it to, that would no doubt have unintended consequences.
Fair enough, this is mostly what i do but since the runtime has been forgiving, i'll need to triple check that. I label all pure money fields as decimal, but am less confident for the ProductQuantity kind of fields. I typically use int for whole numbers but wouldn't be surprised if i used float occasionnally for quantities accepting decimals.
In cases where you have to convert from float/double to decimal you should use an explicit rounding method, not just a raw cast.
> don't cast from double to decimal
I hope this is a compiler warning at the least.
It is an error if you dont explicitly cast: https://learn.microsoft.com/en-us/dotnet/csharp/language-ref...
0.1 is not representable in binary floating point. The old behavior was adding magic that shouldn't be there.
Did they deprecate it, then?
> Wow, what a vicious footgun.
We do too at $dayjob, but use cents for everything and at the end of the calculation we divide by 100 to get the dollar amount.
Exchange rates are 4 decimals, so you would still not get bit by this.
Thanks for that heads up, also:
found this one out the hard way, lol. my C# interface for a vector db stumbled into this right off due to how my tests were written
Ex-MSFT here. Some of MSFT’s biggest and high scale web services run on .NET Core on both Windows and Linux. I am speaking of services in Office, Security and Azure. Think across millions of VMs and servers. The tooling is great, the services are scalable. It’s nothing like .NET Framework.
MSFT’s own services run on .NET and it’s in their own interest to see it thrive and adopted even outside of MSFT.
My current employer, a mature US payroll company also runs on .NET core extensively and safe to say that the development is a breeze. Although I still find that I have to use a paid IDE like Rider or VS. The experience on VS code isn’t that mature.
"Cattle not pets" paradigms mean any of the main languages and runtimes I.e. Java, Node, Python, Go, Rust, PHP can achieve this with the right DevOps / SRE and system design. Albeit some may be more inefficient therefore expensive.
I always liked C#, but the state of IDEs on Linux is what keeps me from using it more. I tried to get a nice setup with VS code, but I agree it's just not good enough. I do use Rider when I work with C#, but I'm not really happy with it either, although I can't really point to the issue with it either.
Couldn you just install VS.NET in a VM and use it then in there on your Linux system?
Microsoft is doing great things with C# and .NET but I feel like the OSS community is struggling with its culture and identity a bit. There has been a lot of rug pulling by some of the most popular open source packages in the .NET world in recent years.
First it was Identity Server, which Microsoft even recommend using in the official documentation (or at least did a year ago), which went closed source and switched names. Then there were others like MediatR and MassTransit who recently went commercial. And now, most recently, several packages are leaning into the "Open Source Maintenance Fee" model which is technically still open source but problematic for any who updates their nuget dependencies without reading all the documents included in the package. I think it hurts extra much because in many of these cases they were very much embraced not only by the community but also by Microsoft just like Identity Server. Like Polly is actually used as a dependency by official Microsoft packages after Microsoft decided to embrace it rather than replace it with their own implementation. Similarly MassTransit and other similar projects objected loudly a few years back when Microsoft were proposing their own messaging/eventing framework and Microsoft backed off.
I'm not saying that OSS maintainers and projects don't deserve to get paid, but none of these projects would have become nearly as popular had they started out commercial. And I feel like this type of thing is happening a lot more in the .NET community recently than in others (although I will concede that I am not as involved in others). Perhaps it is the fact that Microsoft as a big enterprise entity is very much in the driver's seat of the stack or the fact that it is used in a lot of enterprise software or just cultural baggage from 20 years ago where you paid for every library. In any case it feels like there is quite an Enterprise-y sentiment within the entire .NET community. Unfortunately.
Awesome stuff, as usual in .NET releases! The runtime async support is surprising to me... I get that stack traces are more friendly without state machines in the code, but I didn't expect they could switch to a runtime version and have equivalent performance. Cool!
They did that after the learnings with experiment of having a Erlang, Go, Java like implementation for async, await.
Is this a joke, like "hen's teeth"? Those languages don't have async/await.
They don't need to mark functions as needing special compilation / rewriting to support asynchronous use since the runtime provides the necessary mechanism to pause and resume (e.g. stackful continuations). So 'async' is unnecessary and 'await' can be a library function (or remain a keyword of course).
No joke at all, not that I said "like implementation", not that they have async/await.
https://github.com/dotnet/runtimelab/issues/2398
Be sure to read all the way to the bottom,
Friendlier stack traces have an important upside: Cancellation is done by throwing exceptions, but you can't stop it from storing the full stack trace in a string. This can result in a noticeable hitch if the stack is very deep and complex. With a friendlier stack trace, I hope this issue is finally gone.
As the article mentions, stack traces in exceptions are the one place where this change doesn't really move the needle: they were already hiding stack frames related to the async machinery in exception stack traces. The main benefit here is the live call stack in the debugger.
> One especially useful result is a clearer live call stack. Profilers and debuggers can show the asynchronous methods themselves instead of the infrastructure used to execute them. This is a distinction from exception stack traces, which already hide much of that infrastructure.
Maybe exceptions will still gain some benefit because it doesn't have to physically walk so many frames? But the string at the end is likely to be roughly as big as before.
Seems to be a gift of the modern CLR cycling faster with each release since .NET 5 versus the backwards compatibilities the runtime used to have to keep in .NET <= 4 because the same runtime would power multiple versions at a time of the compilers and BCL above it. In the old world "Framework" days after the CLR made the huge changes from 1.0 to 2.0 (for generics and other things) it seemed almost "afraid" to add features, especially for things that compilers could support themselves. The .NET 5+ CLR makes fewer "forward compatibility" guarantees as it now assumes it cycles directly with the compilers and that most deployment methods "bake" the CLR version with the application.
There have been smaller runtime improvements like this one in recent releases, but this one seems a big one to point to to prove that the runtime is truly growing at a faster and more interesting pace than the old days.
i have been seeing a lot better performance, to be honest. It allocates a LOT less.
DotNet finally has an official version manager! Check out dotnetup: https://learn.microsoft.com/en-us/dotnet/core/tools/dotnetup...
We've pragmatically switched all our apps to .NET MAUI and it's been a good ride.
Starting from native mobile apps, we shipped a Windows version as an add-on, and once feature-complete we switched the mobile native apps to the MAUI version.
The debugging phase has been surprisingly short and the app quality is on-par with native.
Too bad Apple never wanted to open Swift to Android, because now .NET MAUI is what Swift should have been.
I have done the same as I already had windows version, but customers were asking for mobile versions as well, so MAUI has been the eventual choice.
Framework has been really immature 2 years ago, but today it is pretty much stable and works fine. Deployment is little bit opinionated than the the WPF builds, but the MAUI keeps improving.
> .NET 11 is a Standard Term Support (STS) release with two years of support under the .NET support policy. STS and Long Term Support (LTS) releases have the same quality and servicing policies; the difference is the length of support. .NET 10 remains a supported LTS option. Stay current with servicing updates whichever release you choose.
They burried the lede on this one.
- .NET 10 is end of life'd on Nov 14th, 2028 - .NET 11 is end of life'd on Nov 9th, 2028
Which means anyone who upgrades to 11 has fewer days of support. But the idea is that you'll upgrade to 12 as soon as it's RTM'd.
The problem with that is I have clients who refuse to budge from 2.1 (or even upgrade to the 2.1 re-release: 2.3).
Nothing new to comment on here. MS has been open about its long term and short term releases based on even and odd numbers. Unsurprisingly, there's a web page that explains it https://dotnet.microsoft.com/en-us/platform/support/policy
This whole thread is a bit of a tangent, but I'm not sure the STS/LTS distinction makes much sense for the end user. STS gets a relatively generous 2 years of support (cf. Java at 6 months) while LTS gets a relatively anemic 3 years of support (cf. Java at 8+ years [1]). Plus, with yearly releases, every LTS expires at the exact same time as its successor STS. Are STS releases generally less stable or more experimental than LTS releases? Otherwise, this feels like a valueless distinction.
[1]: https://www.oracle.com/java/technologies/java-se-support-roa...
Supporting anything for 8 years is a huge commitment. That means 8 concurrent versions needing at least some work all the time.
I think Microsoft regretted those long (10 years) support guarantees they had for .net framework. In the end, it probably slows you down in terms of innovating
Given .NET's release cadence, 7-8 years of LTS would mean 5 concurrent versions: 1 STS release and 4 LTS releases. This is still a lot of old versions to keep updating, of course.
Anyway, the critique was less about the exact length of LTS, and more about the lack of significant difference between LTS and STS.
Yes, STS releases tend to be more experimental and the LTS shores up the gaps.
That may have been true in the past, but that is not the stated policy: "The quality of all releases is the same. The only difference is the length of support."
https://dotnet.microsoft.com/en-us/platform/support/policy/d...
Well, yes, that's the official narrative, but if you seen .net 5, .net 7, and .net 9, and .net 11 and compare them to the LTS that came after them, it's easy to see that pattern.
.net 5 brought records,and then .net 6 brought record structs.
.net 9 previewed the field keyword, and .net 10 released it.
.net 5 brought primary constructors for records, then .net 8 expanded that to classes.
There's a few opposite examples too, but it's a trend I felt over the past decade.
My only wish is they just jumped C# and .NET versions to the year number, since they are incrementing annually anyway, and aren't do semver anymore.
> The problem with that is I have clients who refuse to budge from 2.1 (or even upgrade to the 2.1 re-release: 2.3).
2.3 on .NET Framework reaches end of life on 2027-04-13, so upgrades have to happen soon.
If you mean ASP.NET Core 2.1 or ASP.NET Core 2.3, then they are actually .NET Framework despite the name, and are not the same lineage as .NET 10 and .NET 11.
It's still in support, and led to one of the most confusing release announcements in history: https://devblogs.microsoft.com/dotnet/servicing-release-advi...
ASP.NET Core 2.3 does go end of life in April 2027 however: https://devblogs.microsoft.com/dotnet/aspnet-core-2-3-end-of...
It's not that complicated. ASP.net Core 2.2 and earlier was targeted at .NET Standard, so could run in any CLR (CoreCLR or Framework or even Mono, though I don't know how supported mono was). Asp.Net Core is actually just a csharp library like everything else.
>They burried the lede on this one.
Not really for any groups that commonly use .Net. Even numbers are LTS and odd numbers are not, or at least it's been this way for a while.
>I have clients who refuse to budge from 2.1
Give me their names, sounds like it would be easy to penetrate them again and again and again with AI.
Plus the even/odd LTS/short-term cycle matches Node.js and some others as a release pattern.
C# and .NET are absolute joys to program in. To me they are a slightly more "enterprisey" Ruby on Rails but once you're in it there is a flow to it. Things fit very nicely together, especially with LINQ. Runtime code compilation/"scripting" with Roslyn Compiler Services et al is just great.
What IDE are people using for C# these days? Back in the day (circa 2008) Visual Studio was your only real option.
But I'm on linux now. If I wanted to pick up back up then what should I be using?
I used to use Rider, but its slow and clunky with too many bugs now. It's big advantages were all its code fixes, refactoring etc. These days with AI on linux I just make the AI environment the IDE and use vim as an editor when I need it. Haven't opened Rider for 8/9 months now and stopped my renewal for it. .NET is super easy to use from a command line, so you can do whatever custom set up you like. If you really want an IDE, you could go with Rider, I believe they have a free version now for non commercial work, but I feel like the days of IDEs are kind of numbered.
Same experience. Used Rider exclusively for maybe 8 years, all of them on linux, but clanker advancements in the past six months finally allowed me not to open it at all recently. I believe I even removed it from disk.
Good IDE but they ignored bugs and serious performance problems for too long. The main project I'm working on isn't even that big, maybe 1M SLoC in total, most of them C#, and Rider really struggles with it even on good hardware.
This forced some architectural changes to satisfy the machine (breaking out libraries where they weren't necessary, running code analyzers only in CI, not during development, etc).
So I don't miss the experience.
MS finally have a language server (still in preview, but it works fine) which can be attached to helix in a couple of lines, and that + good knowledge of Unix command line tools is more than enough now.
Visual studio still feels like the only real option to me. I would consider switching to windows if it meant I could use it.
There is a gigantic difference between "technically possible", "practical" and "enjoyable". I had some junior engineers hounding my ass about using ORMs and the liability of manual schema management until they saw how sqlproj and sql compare work in VS proper. You can certainly string this along with 3rd party and custom using other IDEs, but then you don't have as much time and energy to focus on the actual problem. Maintaining a stack just to maintain your stack is pretty crazy to me. I'll happily pay the idiot tax at Home Depot for the Milwaukee option so I can place an invariant on this part of the problem space. I never want to be in a position where I can blame my tools for a bad work product.
I switched to Mac and tried Rider for a while but ended up very happy doing everything in VS Code. I guess in the pre Agent days I would have put more effort into learning Rider but in modern times I just don’t need so many fancy tools for manual refactoring etc. But if you’re still very hands on, it’s an excellent - and possibly superior - Visual Studio replacement.
VSCode is mostly good enough and if you don't mind forking out for a license, Jetbrains Rider is better overall
JetBrains Rider is top-tier.
Visual Studio / Rider / VS Code, depends how much RAM and CPU device has.
I prefer VS but for the performance, I use VS Code. Unable to work in Rider on 32 GB RAM.
As a C dev, the thought that just running an IDE needs 32GB of RAM is insane to me
There was a DJGPP rememberance post a few days ago. Machines had 4-16 Megabytes of RAM back then. Times change.
I've been using Rider for about a decade, but honestly these days I just use Zed. I'm surprised how fast roslyn-language-server is now.
Claude and codex ? Half joking, but can't remember last time I bothered booting up Rider - probably over 6 months - will unsubscribe next year. VSCode is good enough for exploring changes, LLMs are driving the CLI tooling anyway.
Rider if you want a regular IDE. VS Code if reading code from an AI is enough.
I'm using VSCode with remote development on Linux server to develop for Dynamics 365 Dataverse/Sales.. let that sink in :)
Surely VS Code?
I’d go with Rider myself. Vscode?
VS.NET 2022
GitHub Copilot CLI ¯ \ _ ( ツ ) _ / ¯ Then I use a draft PR to review the code
IDE? In the AI era?
C# is really good language with world class tooling
but job offers are kind of boring :/
I moved from PHP to C# jobs because thought it will be more fun.
Have to agree, PHP codebases also were nice organized, bigger repos, mostly web apps / company backoffice apps and so on. Personally, products were more interesting.
With today dotnet state, it really suits well for API development / microservices / integrations / we had many workers - jobs running, queue subscribers, K8S, good test coverage, open telemetry.
Even migrating from .NET Framework wasn't so big pain.
Job postings sucks nowadays for all stacks.
Must depend where you live. In the Midwest, like Wisconsin, there are plenty of jobs for .NET
Does runtime async mean stackful coroutines? It's not obvious what exactly the state machines are replaced by, if not automatically inserted yield() calls.
I took it to mean that coroutines are still stackless but that yielding is now an operation the runtime knows about instead of being compiled out by the compiler.
For the features that overlap between golang and .Net, can someone please let me know why people choose .Net over golang for non legacy systems? As far as my limited knowledge goes, go is much more efficient in syntax and memory.
The languages are very different. C# has much more powerful abstractions and is a much larger language in general. That is both an advantage and a disadvantage. Modern C# is also very fast, I wouldn't bet on Go winning that comparison.
My experience with Go is a while back, before it got generics. While I liked the overall idea of a language with little syntax and a focus on simplicity, I didn't really like Go in the end. It was a weird mix of too low-level and high level code. It exposed low level details that I don't need to think about in C#, and it is missing convenient abstractions like LINQ.
Personally, I like async/await in C# more than Go concurrency. I know this is heretical here, but I don't care about function coloring or any of the abstract advantages of other concurrency approaches. Async/Await in C# works for me (in non-legacy code) and is easy to understand.
the main problem with the .net async/await is the immediate execution of a call returning a Task, without control over where it may execute. F#'s asnyc was way saner and easier to understand in my opinion.
I'd beg to differ on the easy to understand... last time i worked with this stuff like ConfigureAwait only has cargo cults and no real good explanation, and wasn't covered in any recent version of eg. CLR via C#... Distributed tracing and thread management were totally bitten by async, also the way iterators and LINQ were broken... that was when I got off the train, as it broke the best abstractions for line of business code so that instead of designing an IO pipeline lazy developers could simple put Task.Run around blocking IO calls at the bottom of the codebas, and ... yeah, we are back at function coloring.
This brings me to: I winder for what kind of apps did you find it a delight, because for extensive data wrangling leveraging multiple calls it was a pain.
You don't need ConfigureAwait() at all in modern C# (unless you're writing desktop UI code, I don't know enough about that to comment there). In a modern ASP.NET Core web application you don't care about which thread your Task executes, there is no synchronization context at all.
I'm using C# for the backend of a web application, not legacy but .NET Core from the start.
> unless you're writing desktop UI code
Indeed, you need a crapload of ConfigureAwait() in desktop UI code, and it infects all underlying libraries that might have to run inside a desktop app. The worst thing they ever added to our beautiful language. It's a dream to work in ASP.NET without needing to pepper every await with a ConfigureAwait().
That said, I don't agree with the characterization of ConfigureAwait() as un-understandable. We understand it. We just hate it.
In case you were not aware, you can use Fody to automatically add ConfigureAwait everywhere it's needed at build time: https://github.com/fody/configureawait
Sometimes you do care where it executes, and then it starts to become a problem ;).
Something which was as easy as an IO call in the middle of a function running on a thread pool becomes a PITA if the IO call is cleverly async-ized (by wrapping the original sync IO method into a Task.Run(), but actually this is the better case!) ad then removed from the public API... (cannot recall the exact library, but it was a pain, as it was some core stuff, maybe vendored by MS themselves)
Also back then it caused us loosing correlation IDs as those were thread locals in traditional pre-async ASP.Net Core (think version 0.9-2.0 when I worked on that part, then up until core 4 and the async mania continuos broke readable calculation code in LINQ, TPL based performant data pipelines, etc...) and with async execution started to migrate between threads... If you needed high performance you already used threads, because it was not a classic web application, but a specialized optimization algorithm with a web api... and if you need more than CRUD, and use all the cores (and SIMD) to squeeze out all performance you have, you hit these problems right away. These all could be solved, but it actually broke stuff, a lot of stuff, as developers jumped on the fad, and it their sync code was turned into a .ConfigureAwait(false /* Microsoft blogs talk a lot about it without a clear explanation, so every cool kid does this */).GetAwaiter().GetResult()... then you some of your threaded code silently broken if you dare used Thread Locals... Faded memories, but that was when they f'ed up the C# language.
That's why I emphasized modern .NET. All I/O should be using async methods through the entire application. Wrapping sync code in async causes troubles, but that is entirely avoidable in new code. That's the price you have to pay to make this particular abstraction work well, everything I/O must be async.
Go isn't as much fun. And is usually slower for my work until I bump up the GC and then it uses roughly the same amount of memory.
I have also found c# async to be lighter than go's concurrency story, and dotnet11 is going to make that much more clear.
Now: I write software for fun, not for money.
So for a requirement where both golang and .net would suffice, would you choose .Net over golang?
Yes. Unless I really need CSP, but then I would much rather use f#+hopac because CML is the bee's knees.
For general concurrency where async doesn't suffice, go is nicer than C#, but hopac is nicer than go.
If go had proper concurrentML I am pretty sure I would use go a lot more. Again: this is for hobby projects where I get to choose freely.
Based Hopac enjoyer. I built the Spiral's language server using it. I have to admit though that these days I just use native .NET channels for all my message passing needs.
I vastly prefer cml, but hopac has a lot of missed opportunities. I vibe coded my own language on top of c# that has a cml that is much more integrated with the language (and no other constructs are available :)). Being able to use events for things like reading from ports, so that you can select over reads and only the winning read actually takes anything from the port is more than nice to have. Or having STM that can be run as an event, so that you can choose over a transaction or a timeout in extreme congestion. Since it is composable, you can abstract away a lot of the details behind simple function calls.
I am kind of curious if it becomes faster when moving to dotnet11.
Go is not much more efficient, compared to NativeAOT - though it can be faster in some areas. The big reason why someone would want to use C# is that it's a more expressive language. You can even write your DB queries with LINQ (C# lets you express code as data), making them type-safe.
Modern .net is surprisingly performant while also being very high level if needed. So it is very close to being a "learn once" ecosystem. This means you use it to write code for a much broader range of usecases than others, you will have a broader / more mature ecosystem than others, and your potential hiring market is more vast.
That is an urban myth, CLR was designed with support for C++ in mind.
Also Go is at the stage of C# .NET 2.0, and still plenty of lacking language features.
I really think the creator of C# made a disservice to the .NET community by picking Go for the Typescript rewrite.
Migrating to Go was easier because it was easier to port the code instead of doing a rewrite. Go matched the easiest to the way the code was written. C#, F# or Rust would have required a re-write instead of just simple changes.
https://devblogs.microsoft.com/typescript/typescript-native-...
Go still has a better deployment story. Single static binary deployment are king. On .Net 8 I was hitting random compatibility issues with single binary application bundles, with System.Data.SQLite, and other libraries. (had to use that one and not he Microsoft one for some legacy compatibility reason, maybe around Guid handling maybe)
From your description you certainly weren't using AOT compilation, and forgetting about CGO.
Static linking as the only way in most platforms, we moved into other mechanisms for a reason.
In Go the only way to do plugins, without shipping everything as source code is to use microservices, the more fashionable name for multiple process with OS IPC.
I'd presume you can still do dlopen despite your binary being statically linked... but I have not expermented in the topic, I did dlopen in Linux libc, and Assembly.Load and P/Invoke in .Net (no AOT).
I wasn't using AOT, it was not something I needed/wanted, but for a TypeScript compiler it would make more sense. I only tried single file binary compilation, as I didn't want to deploy the runtimes, wanted to have them embedded to the air-gapped systems in a simple copy and runway (no installers, etc).
Yeah, but that beats the purpose of static linking in first place, especially if the plugins require access to the host process APIs.
Doable, but requires a bit more of extra work, and Go plugin package is basically abandoned, it was not yet been removed due to backwards compatibility.
Is cp -r/xcopy/robocopy a folder (with or without tar/zip wrappers) that much harder than single file deployment?
Yes. If you need to install runtimes.
In .NET 5+ you shouldn't need to install runtimes, though. Self-contained folder full of DLLs output is the default publishing build today and very reliable at this point (and doesn't have the same restrictions as trying to AOT compile and treeshake everything into a single self-contained EXE).
Obviously, there's a trade off in size having a whole folder of files, some of which would be redundant with other applications, but folder of files using xcopy only deployments have seemed to become the standard in .NET. (Or perhaps more commonly, folder full of files added to a minimal "no runtime" Docker container overlay.)
I don't think I've ever seen a global install of a .NET 5+ runtime. I wouldn't be surprised if some Enterprise was doing it for some belief in HD space savings, but I've certainly not seen it. Just file folder deploys.
> still plenty of lacking language features
Fans of Go (myself included) prefer this. The small feature set is a feature, not a bug.
The history of generics and Go is enough to keep me away from that mess of a language forever.
Go seems actively hostile to it's users, especially in comparison to C#.
with Go, they have made one commitment. The language will always be backwards compatible. So code written in today's go should still be able to be compiled in 30 years using the compiler of the day. That means when adding new language features, they are very, very, very particular and stubborn about it.
Every mature programming language places a very high priority of backwards compatibility, including C#. Microsoft introduced generics, LINQ, async/await, pattern matching, records, nullable reference types, and numerous other features while maintaining a very high degree of backwards compatibility.
But either way, this merely provides an excuse as to why Go is actively hostile to it's users. But backwards compatibility and language evolution are not mutually exclusive.
I thought they changed how variables are captured in for loops at one point didn't they?
There have been changes in Go already, even if minimal.
I know it's not quite as important in the agentic world, but the .NET tooling (across Visual Studio, JetBrains Rider, and C# Dev Kit for VS Code) is just awesome. Some people might frown upon full-blown IDEs, but sometimes they're just incredibly handy.
Even the non-visual stuff is pretty awesome, claude is able to trace and profile .NET code without skills or MCPs.
Every new release of Go brings some of the features that have been available for twenty years in .NET (and every other language), like generics. Some features, like ergonomic error handling (whether or not you think unchecked exceptions are a good idea), Go has said will never be available. There isn't much to recommend about Go. It's fast to do the mediocre thing, faster than any language, but for a serious code-base intended to be maintained long-term, C# has better libraries, established patterns and frameworks, abstraction features, developer ergonomics, etc.
The tooling and development UX.
Put things like their remote debugging experiences side-by-side and you will quickly learn why some of us go for the Microsoft tools rather than the others.
Exactly C# with .NET is batteries included type of ecosystem where most of stuff is done by Microsoft and if someone is experienced with the stack he can whip up enterprise ready app in no time.
I don’t have much experience with Go lang but what I have seen, there is lots of things one has to implement on his own. Which has its own merits, but to go from 0 to full fledged enterprise app you need more time.
I'm not a .NET person by any stretch and have been more of a Go person traditionally, but I think from what I've seen IRL, this is accurate.
Go is trying to be like C. Less language features (until recently...), so to solve problems you write more code. Intentionally simple language. Intentionally need a lot of code to implement something substantial. .NET seems more frameworks driven. Go traditionally has eschewed frameworks for doing stuff much like C tends to eschew framework type stuff and prefers smallest dependency reliance as possible.
Go is also very much a batteries included language when it comes to their standard library. Like it even comes with an ORM. Most people use the Http server that comes with standard library. even more so now that the router for it is a lot better.
Compare the ecosystems of .NET vs golang. No comparison whatsover. IoT, mobile, web (front and backend), cloud, etc. I think Go lacks variety and is more focused on traditional greybeard areas.
Native interop is much better in .NET, that matters a lot for some systems.
There's one (usually minor) caveat here, which is around strings. In .NET, like Windows*, strings are UTF-16. If you call into native code that uses UTF-8 strings extensively, then they have to be marshalled (when passed to native code) and unmarshalled (when received from native code), which takes additional time and memory. I've run into this lately, which has led me to write special CStr/CString types (idea borrowed from Rust) to avoid unnecessary, especially round-trip, (un-)marshalling.
* = Technically, Windows now supports the UTF-8 "code page" where applications can use UTF-8 natively, but all NT system calls use UTF-16, so the conversion still has to happen somewhere
C# in .NET 11 adds support for UTF-8 string literals (`ReadOnlySpan<byte> str = "Some sample string."u8;`) which is a step to making such interop easier.
The Runtime has explored attempting "UTF-8 native" string storage as a platform feature especially for Linux FFI, but yeah Windows using UTF-16 is still a compatibility issue where platforms themselves just don't agree.
For me, I use both languages. But there's a lot of things which are only like a couple lines or very easy to do in .NET but to do it in Go is a lot of work.
Some example.
Open API document generation using type handlers doing this in go was a pain in .net is comes out of the box.
A lot of people underestimate the power of extension methods.
.nets metadata system is compiled time safe where Go is not and this has burnt me a lot.
I really like go for command line tools Small binary sizes I lovely.
go's standard library is significantly more hackable than .nets there have been a few things where I've ended up using Go because to do it in .net would require me maintaining a fork of the standard library.
Go makes composing data insanely simple. where in .NET you can have an equivalency but it will be done via interfaces.
Go's Dark Type System is both great and really annoying.
C#'s stricter type system when it comes to interfaces can be really handy as that can be used as metadata to change how data is processed and enforce compile time checks.
At former jobs, because people know it and switching from .Net <-> Go generally isn't worth the time to learn other language.
Sure, having a runtime is downside but difference in container size for your average CRUD App doesn't really matter. Performance IME is very similar.
Ops type here.
golang also has a runtime
Technically yes but since it ships with binary, effectively, most people don't care.
> go is much more efficient in syntax and memory
I'm going to talk about the second part first. .NET is often deployed with a JIT compiler runtime like Java. That means that hello-world scripts use a lot more memory than Go, but that difference becomes a lot smaller on real-world programs and .NET can use less memory than Go depending on what you're doing.
C#'s concurrency can also be more efficient than Go's since it's coroutine based async/await is a lightweight state machine rather than green threads. That's not to say Go is especially heavy or anything, but it is more overhead.
In terms of "efficient in syntax", Go has a lot fewer features. That's one of Go's biggest strengths and weaknesses.
Here's an example. With Go, you just have struct tags: strings of anything that can be attached to a field in a struct. Let's say you have a User struct and you want to make it so the JSON won't have the user's email in it. You do: Email string `json:"-"`. But what if you mess up and write "jsom" or `json:-` without the quotes? It's basically a meaningless string to the compiler. With C#/.NET, you have annotatons/attributes that are a part of the language and checked as types. You'd do: [JsonIgnore] public string Email. JsonIgnore is an attribute class so your IDE can help you.
There's a lot in Go where the answer kinda becomes "f-it, we'll just use strings instead of having something meaningful." Go talks about how important error handling is, but then you get an error from a function and it's basically just a string. If you're doing good error handling, you want to handle different errors differently, but that's not easy in Go. Frankly, it's not easy in most languages, but this is definitely an area where it felt like Go gave up on creating something good in order to get something out the door quickly enough that Google didn't cancel the project. In a language like Zig, errors are enums and you get exhaustiveness checking. With Go, someone could add another error state to something you're using and you'd never know - or they could change the phrasing of an existing error and you'd silently stop matching on it.
Go's lambda syntax is clunky. func(x MyType) string { x.ToString(); } vs (x) => x.ToString()
There's lots of stuff like that. I'm not arguing that Go is bad, but it has a ton less stuff and that's both a blessing and a curse.
What's go's equivalent of LINQ?
SQL strings
Linq is not an ORM.
It is universal interface to query diverse sources of data. Most often you use linq to query arrays or lists. This is implemented as a set of extension methods on IEnumerable. Implementations for other data sources exists, most prominently Entity Framework which translates linq queries to sql queries.
Modern C# is a much more powerful and expressive language than Go: iterators, collection expressions, records, async await, source generator, much more powerful generics, low level memory management features like Span<T> and ref struct, etc. It is closer to C++ with opt out GC/opt in AoT than it is the old Java comparison days.
Go's advantage is mostly the fast compile speed, goroutines, profiling, and a very opinionated formatter. Neither has an overwhelming advantage over the other though.
.net has a much stronger ecosystem and more experienced developers.
Golang is a fantastic delightful language, but it's still missing a lot of stuff that's existed in C# for years.
However, do you *need* all those .net tricks?
Golang is usually enough for a small project you need to build in a weekend. Scaling it to a mono repo with 200 developers might be a bit difficult
One thing I really, really wish Go had was met a daughter that was checked at compile time.
me miss spelling a tag has burnt me so many times.
This comment is comedic perfection
Metadaughter? Like metadata but with an Australian accent?
Well for me, that's like asking why don't you write everything in Erlang rather than C#.
Go is for me just a, hmm, concurrency programming language. Its popularity stems just from being pushed by Google, and the cosmetics of using braces. Despite what pundits say, it's completely and utterly not like C.
While C# is a successful and comfortable everything-language. It is blazing fast, has amazing tooling, you can get going to prototyping, then enterprise code, its serverside handling is excellent. It has many low level features and built-in data structures. It's I would say preferable for almost any kind of work except scripting. (I really don't like C# scripting.)
> except scripting
PowerShell says hello :)
In addition to what the others have said, I also enjoy the extremely robust ringtone reflection system in .NET. I don't know of any other systems that provide more complete detail. Maybe Java, though it's been a while since I've used it. It makes reflection in other languages feel prehistoricaly primitive.
I wonder what this means for custom async implementations in the long run. I was considering using my own AsyncBuilder to bring over a rudimentary "computation expression" capability. But that might be out of question now.
My first job out of college was at Microsoft, probably at the height of its powers. I was a developer on the .NET team and helped bring SQLCLR to life, the regrettable decision to let you run .NET code inside SQL Server. That job opened a lot of interesting doors for me. The weather sucked though.
To all the .NET outsiders, C# is indeed the .NET darling but you can also use F# if you feel like living in the future. Or VB if you feel nostalgic. So in .Net land, C# is not destiny. It is a choice
I feel like VB is a "better" Python for beginners and F# is a much better Python for data scientists.
The tooling ecosystem in Python is just so bad that I can barely stomach it after spending the last 20 years in .NET. At times, when supporting analysts at work wanting to write a little glue code, I've found their main complaint about C# is "curly braces" (which seems like a really odd thing to complain about, but ok) and I've found it easier to teach them a little VB than to figure out whatever the hell is going on with dependency and runtime version management in Python.
And then, for the folks who go deep on Python for ML tasks, I've mostly found them caring now about specific libraries than the language itself. I then show them how .NET has equivalent or better libraries available and that a lot of the code-golf stuff they do in Python has a much cleaner version in F#.
What do you recommend for numpy, duckdb, pandas/polars and pytorch in python?
Polars.NET is actually not bad already.
The data ecosystem is not good though e.g. try doing a ridge regression in dotnet
That’s always been my impression. Working with azure and mssql etc - great. Slightly off the enterprise app path - immediately dragons and lack of drivers, convenience and gpu, etc etc.
Ugh, I agree, Python tooling makes me want to pull my hair out every now and then. And then I weep for IronPython... it could have been a great blend, if it hadn't been abandoned. Though I can imagine that trying to "marry" Python to .NET became more and more of an uphill battle over the years. I think the last maintenance release was about two years ago.
> C# is indeed the .NET darling but you can also use F# if you feel like living in the future. Or VB if you feel nostalgic
Or PowerShell if you want to hash out something really quick.
I find it striking that the article provides this enthusiastic example of a new feature:
It's "good" that we have a hashset with syntactically four entries and no error/warning about duplicate keys and it ends up with just two and that there's an entry "with(...)" that's not actually an entry. And that's even before we get into how something called a StringComparer is providing a hash function and how OrdinalIgnoreCase is an underdefined upper-casing system rather than a true case-folding one, even if those are historical.That `with` inside of the collection expression feels really weird to me, I would have expected it before or after it.
I am looking forward to async stack traces being more legible. It might encourage developers to actually read and use them.
My web browser is anxiously awaiting the "Performance Improvements in .NET 11" article.
That always comes out much earlier with some of the RC versions.
https://devblogs.microsoft.com/dotnet/performance-improvemen...
Discussion: https://news.ycombinator.com/item?id=49711424
You’re looking for “Performance Improvements in .NET Developers”.
I loved .NET, but it is slow. I abandoned it after I learned that it doesn't even compile to machine code, so it will never be fast.
> doesn't even compile to machine code
It does: https://learn.microsoft.com/en-us/dotnet/core/deploying/nati...
It is, in fact, not slow at all
what are you talking about? even without aot, dotnet offers pretty stellar performance.
No mention of Avalonia. I remember it was chosen as a back-end for MAUI awhile ago.
Good news: dotnet does everything well Bad news: Microsoft
C# finally has Sum types.
Not quite discriminated unions, but close. With an exhaustive checker with switch statements.
Now if only it made creating unique types easy, so I wouldn’t have to use a library like VOgen, the language would be nearly as good as F#
Hey there. C# language designer here. Working on that here: https://github.com/dotnet/csharplang/tree/main/proposals/dis...
You can start reading through it starting at: https://github.com/dotnet/csharplang/blob/main/proposals/dis...
Definitely want to move to get this in asap :)
Merci Cyrus Jan
Makes me confident in C# to see the language in the hands of talented and clearly passionate designers like you.
As for your DU proposal, between Union types and enums, I think you know that anyone who has used TS or Rust or F# or OCaml will have a strong preference for the Union based syntax.
Good luck with the effort to get it added!
C# is a really great language, and I hope I can keep using it. I like Microsoft’s ecosystem, but personally, I’d like to see its cross-platform support strengthened a bit more.
What's missing from cross-platform support? You can basically run it everywhere already?
MAUI is still not great, is it? I remember Linux wasn't supported either. I tried MAUI on macOS, but honestly it's hard to consider that supported. Many people use WinForms and WPF, but these don't support cross-platform, and honestly WinUI 3 is also lacking. On the server side, TypeScript is actually better (both are Microsoft languages).
In fact, desktop apps are mostly used for factories or Windows-only programs, which is a bit disappointing. It's a very good language for making games and the most widely used language for factory programming, but whether to build a product with it is a bit ambiguous. I'd use it when making a program for a specific customer, but for a product I'd probably use TypeScript.
If anyone wants cross-platform desktop GUIs to actually happen (in the way that people mean when they talk about this stuff—i.e. neither Electron nor Qt), then they're going to have to implement Cocoa for Windows and Linux. There is no other way.
If you're limiting yourself to Microsoft's options then I'm going to have to say that's a you problem. Avalonia is pretty great. What do you use when you're building apps in other languages?
> On the server side, TypeScript is actually better
I like TypeScript but I'd never pick it over C# on the server side unless I had to do React SSR for some reason.
React SSR is now also supported by the community! https://github.com/davidwhitney/JsxCore
The issue is that it's not a hobby, but a delivery. For delivery, you have to check commercial compatibility and long-term maintainability, so I'm not sure. Actually, I don't particularly like the XAML model itself, but the problem is the LTS policy and long-term viability. In delivery, technology choice is important. Because what can be maintained later is what matters. I have learned TypeScript, but I don't think you're wrong. It's sufficiently understandable for personal development and hobbies, but from a delivery perspective, it could be considered a risky choice.
I often build ERPs too, and for delivery I usually use TypeScript with Electron. C#'s biggest problem is mobile support. It's very strong on the game side, but weak on most phones. TypeScript, on the other hand, uses a bit more memory but is easier to wrap for mobile. You can just use Capacitor alongside Electron.
In my case, it's not a huge project—it's usually a small ERP dedicated to a local company in the region, so even if there are many employees, it's often only 100–200 people. At that scale, TypeScript holds up well enough. (For factory MES, there are tens of thousands of data points, but in that case it's usually fixed to C#.)
In this context, the mobile option in the ERP is good for charging customers, and it offers good extensibility as a reference option.
Unless WASM performance improves, there's still a bit of latency. On top of that, in my country there are few maintenance personnel.
Avalonia has enterprise support available if you need it. It also supports mobile (native AOT, not WASM).
I do agree that it's hard to beat web tech though. Great support across all platforms. Just don't forget to optimize.
You lost me a bit - everything you mention is client side, not server side.
Related: What's new in the .NET 11 runtime (10.11.2026)
https://news.ycombinator.com/item?id=50439284
I feel like they've done a lot to resolve a lot of the confusing versioning and runtime compatibility problems that drove away non-Windows developers from using .NET.
However they still suffer from an incredible branding problem.
It's really too bad because as a runtime it's fairly incredible, and F# in particular is fun to work with. They also have good cross-platform support and tooling, which they didn't have a number of years ago.
Combine that with the fact that nobody really writes code anyway thanks to AI, and this could be a slam dunk runtime for an AI-native development environment.
But it will never happen.
It's actually crazy what the agent can do with that.
I am actually quite convinced that having solid framework as a base will be a huge win for building business line applications with use of LLM agents.
They have a section for breaking changes in .NET 11, containing a few dozen items. It starts with "If you're migrating an app to .NET 11", as if you'd ever have a choice. Microsoft forces you to upgrade every 2 years, and they ship breaking changes. It is completely unacceptable that these two coexist, and I'm confused why more people aren't angry about it.
Either you should not be forced to upgrade, or backward compatibility should be guaranteed -- not both. But Microsoft is happy to say "hey, we're going to completely abandon support for every version of .NET every 2 years, and if you can't keep up to date then everything that goes wrong is all your fault." Meanwhile, they have no problem breaking your code.
This essentially kills the possibility of just writing a bunch of small, useful libraries, and having them be useful forever. It is the antithesis of a healthy software ecosystem. Are you really expecting that every single open source library that is referenced by a reference of your references is going to be maintained in the face of breaking changes every 2 years? If not, then upgrading your version of .NET is just a gamble that things won't randomly break. But you must upgrade, or you're a bad developer who's leaving your apps vulnerable and every automated security checking system in the world will yell at you about how awful you are. You must upgrade everything you have ever written every two years, and hope that every library you've ever depended on does the same, or you are putting yourself at risk of some breaking change sneaking its way in, or leaving yourself vulnerable to a critical security issue that Microsoft refuses to back-apply.
I cannot understand why I'm the only one shouting from the rooftops about this. It's completely unacceptable to me.
Plenty of systems still use .NET 8/9 today. It's very likely that there are some websites that we use daily running on .NET 8 with ASP. Microsoft never barged in our server room and forced us to upgrade .NET, but YMMV.
I think a lot of this stems from the fact that we live in a "why we can't have nice things" world. As long as there are bad actors, there will need to be security updates. So if you don't update, and Microsoft no longer provides security updates for that version, you're in quite the bind.
Godot still defaults to .net 8
If I wrote a useful library a few years back and it targeted net472 and net6.0 then… it just keeps targeting those? I can still use it from a net48 and net10.0 app just fine?
No, you cannot use it "just fine". There could be any number of breaking changes to your library between .NET 6 and .NET 10, and you're not guaranteed that a build will catch them. Have you painstakingly reviewed every line of code against every breaking change that Microsoft introduces with every new runtime?
I personally saw, this week, an internal library that worked in .NET 8 but threw runtime errors when built in .NET 10. Previously working code can just break in subtle ways, and it could be anywhere, in any library in your chain of dependencies. In an ecosystem where you are forced to upgrade every 2 years, this is not OK. This is not a feasible way to build an ecosystem of interconnected libraries, and I believe the only reason it's not more painfully obvious is that .NET Core just isn't that old yet. This is going to hurt more and more as time goes on.
There's a reason Microsoft was obsessed with backward compatibility between 1980 and 2016. This never happened with .NET Framework. I don't know why Microsoft suddenly decided backward compatibility no longer matters, but it's a big mistake.
Yes you can, so I don't know what OP is complaining about.
The library might behave differently when run under a newer NET version due to breaking changes in NET.
https://learn.microsoft.com/en-us/dotnet/core/compatibility/...