As a primarily C# developer who has done some game engine work, I recently gave Godot a go for licensing reasons. Apart from some quirkiness I'm fairly impressed. Much nicer C# support compared to Unity. I've done a fair bit of Unreal C++ but to be honest unless you really need the performance that's just too much hard work.
Having said that getting the code working properly with a nice 3D UI is my priority, not having a slick game with some code doing some mundane stuff in the background.
I can't comment on Godot or Unity which both use primarily C#.
Unreal engine which uses C++ primarily, has the problem that it's a humongous mostly legacy code macro heavy system .
If anything being proficient in C++ before you start is harmful because of the puckering of orifices when you hear about it's mostly quirky powerful macros all the way down.
Never worked with it myself but I've always heard the people who do describe it as Unreal c++ because to them it's completely different than regular c++ and this must be one of the reasons why
I agree. I came to Unreal with only a basic level of C++. Having Unreal handle memory management for you was useful, but I can imagine the chaos an Unreal-only C++ developer might cause when unleashed on another C++ codebase.
It's 100% completely usable for that purpose. To the point where I'm internally using Godot as a backend for my engine, using it for rendering/system events.
It's also really good for a hybrid setup where you use their editor to design scenes, and then you can load/instantiate the scenes at runtime how/when you want. Or, just programmatically creates scenes yourself from code. You can really do whatever you want.
Fans of LINQ may enjoy ZLinq[0], which is a less versatile but much more performant way to write LINQ-like queries. I certainly use a lot of (Z)Linq in my code; the performance tradeoff is just fine for one-off initialization, UI code, editor tooling, etc.
Zero allocation impl? very cool! Arguably, the original runtime should have done this in the first place given the limitations/tradeoffs aren't so bad, but I guess that ship sailed 20 years ago (man that hurts)
The modern .NET runtime can get devirtualize interface calls and eliminate temporary object allocations in some scenarios. It's a bit of a black box - who knows when it actually works? - but still, it's a nice boost here and there.
Most .NET projects that Linq originally targeted ran in so called "Server" and/or "Workstation" GCs (.NET has had very generic public names for its GCs for a long time which were also somewhat misnomers because even some Desktop apps would run in "Server" GC and it was possible for vice versa [0]) where allocations were cheap, garbage collection was relatively cheap (because both GCs were multi-generational, had strong [but different] tuning for their generations, etc).
Unity inherited a much simpler Boehm GC from Mono. Under a (single generation) Boehm GC allocations are a bit more expensive and garbage collection sometimes a lot more expensive. (A Boehm GC was much easier for C++ engine code to understand/interact with, which is also part of why the .NET modernization project for Unity got so complicated and still has such a ways to go left.)
[0] Fun aside: in fact, modern docker advice for .NET is to switch "server applications" to use "Workstation GC" if you need to stack multiple containers on the same host because of differences in expected memory usage.
A lot of game devs are terrible programmers. A friend of mine 10 years ago asked me for help with his Unity project. He is not a tech savvy person but we both took programming in high school, enough for him to make small games with a lot of tutorials and stack overflow.
His codebase was horrible, a lot of logic that I would have already though of abstracting away. For example saving dialogs on json files and the conditions for that dialog to trigger for that NPC as some sort of finite state machine that can be represented with a series of sequential flags. He had a single file that was about 15k lines full of `if (condition && condition) || (condition && condition)` statements. He didn't seem to see the issue, it just worked.
That's when I understood some people just care about game development and doing cool stuff and don't care at all about programming, good practices or structured code. And that's perfectly fine.
Most indie game dev projects start as some small weekend project just to feel things out. Then it starts to become fun so we work on it another week. Then after a couple months we start to think that maybe the game has potential. Then we're 5 years into a project and have no clue how we got there. It becomes a giant jenga tower where moving any one block can completely collapse the whole project, so we learn the hard way that nobody should ever refactor. Pretty much the only people who do refactor end up restarting their project from scratch, getting frustrated because they can't capture their original feel of the game, then ultimately abandon the project entirely.
And for professional game dev projects, it's all built on a foundation of some scrappy little indie project from decades ago.
Some industries are all about making their code public and making it super clean and polished as a point of pride. Games, like movies and sausage, are disgusting to see behind the scenes. They're just piles of scraps and weird tricks that look great unless you get down and examine it too closely. And most people aren't looking that closely, so wasting that time and effort is pointless.
Since it's a multi-discipline craft it's hard to get good at every aspect for indie development, focusing most of the effort on one or two aspects. I think the programming aspect for indie games typically matters very little unless it hurts performance or causes bugs, and the things the user interacts with end up mattering a lot more.
Every web developer I've met has specialised in one area or another, even if they claim the title of "Full Stack".
Problem is that this kind of code often is brittle, full of bugs and unhandled edge cases, and evolution and maintenance is horror. But if it’s all you know you might never question it.
Often but not always, and if they're a solo developer then maintenance might not be too bad as they might be able to keep all the logic in their head. I'm not advocating for that kind of approach, but if it lets people focus on things that the player will actually notice like the gameplay, graphics, sound, story or art then hey, what's a little shortcut?
I’ve seen worse in enterprise shops and then I’ve gotten into nasty arguments with people who don’t care about programming. They can’t be wrong.
C# is a high level language that can handle a degree of sloppy programming.
I was working on a small tool yesterday. It was easier to vibe code it from scratch in C# than to modify an existing Rust project.
The only weird part is VS Code Copilot couldn’t figure out how to build it via the dotnet cli and I had to install VS Studio. After that everything was fine.
I would say that C# is "less colored" than Rust. Handling a moved argument, a borrowed argument, a copied argument is different in Rust, you have to think upfront about memory lifetimes. If you are wrong the first time, changing the ownership is not a simple localized refactoring.
I think as a solo developer there's actually a good argument for increasing code density and coupling (things which in large multi developer projects are seen as spaghetti), as it can help you keep a lot of that code in mental and visual context at one time.
It loses flexibility and readability for others, but you don't usually have enough time to concern yourself with such flexibility if you're working on a project by yourself, and you're not concerned about onboarding other developers and having them understand your code. The upshot is then that as a single person "bad code" is often highly effective code, and "clean code" is expensive code that buys you a lot of stuff you don't need or want.
I say this as a boring enterprise developer who at work is highly concerned with appropriate abstractions etc. imo there's no universally good approach, what is optimal is context dependent. Although there are some core features of code like consistency and strong conventions which are fairly universally helpful, this represents a small fraction of best practices.
This is pretty anti-thetical to most good practices but the older and more experienced I get the more(13 years as a C# dev) I think copy & pasting sections of code is wayyyyyy more appropriate than extracting into a method/class/library or other forms of abstraction.
Everything starts out with good intentions when someone comes along and says “hey you could make that an abstraction” and I just clench my jaw because I’ve seen that happen so much and then that simple clean abstraction eventually ends up being a horrible 1000 line monster that barely anyone understands and no one wants to change.
I agree with everything except for it being anti-thetical to good practice. I have noticed a lot of experienced devs agree with that sentiment.
It has been a pretty common trend for the last few years of people breaking out of the “OOP style programming” and practices they were taught at university. I am not saying avoiding things like over abstraction is new, but I do think there is a newer generation of programmers who have been taught and warned about drawbacks from practices like that.
Similarly, my anecdotal experience tells me more newer game devs are aware of basic memory practices being better than overly complex OOP code. Think flat arrays and simple cache alignment over something abstract and over engineered
100% this. All the abstractions and OOP stuff make you end up with a codebase where half the code doesn’t actually DO anything in the product itself, it just connects to other code! It also becomes impossible to follow the flow of execution because it passes through dozens of files and layers of abstraction.
Gamedev at a high level is similar in spirit to distributed systems or compilers in that it's intensely multi-disciplinary with hard constraints. In gamedev you have a main thread that you're always sweating about submilliseconds, especially with worker threads interfering with it. Processor, memory, bus characteristics, GPUs, cooling, etc. all matter.
This leads to e.g., everyday AoS vs. SoA, pooling and burst compile, to the classic fast inverse square root because of hardware at the time. Relentless optimization of hot paths produces code that's about performance, not abstraction. Then there's shaders, which are effectively a different programming model targeting different hardware entirely. Now add support for multiple operating systems, consoles, whatever. The list goes on.
Now, all of that doesn't obviate the value of design and craft, so I don't agree that it's "perfectly fine". There are plenty of programmers weak on these two axes in most any domain, but it's worth noting gamedev is a special case that significantly distorts what good code, or at least good-enough code, looks like.
Games start as little experiments and end up as Frankensteins. This is their nature; you're more sculpting a thing by building it and experiencing it rather than designing it a priori with systemic elegance in mind.
And that first bit only really matters if you're making a fast-paced game with sophisticated graphics. Most indie games can get away with horribly inefficient code.
To be a bit more charitable: I'd say that generally games involve a lot more special-casing than most code, and more planned out scripts (in the movie sense) of things happening, which tend to be antithetical to good coding practice, and encourage spaghetti, which begets more. In my experience, games that are procedural tend to be much cleaner code-wise, because they tend to fit the model of cleaner code better.
I think game engine tooling tends to encourage bad code too, lots of game engine make it hard to do everything in code, rather things are special cased through UIs or magic in the engine, which means you often can't use all the normal language features, and have to do things in awkward ways to fit the tooling.
IME you have so little reuse, and ship on a fixed schedule regardless of code quality & bugs this really isn't as critical as software built with the intent of lasting a long time & evolving. The games I've worked on (in hindsight) feel a lot like "vibe-coded without AI".
The other day a professional gamedev was arguing with me that's Transform.GetChild to get a reference to a component was not a bad practice. His argument is that it's what every other firms in his area is also doing. I do hope that was not the truth and he was just trying to win the argument.
For solo hobby dev it is a lot more acceptable. After all they're also terrible 3d modeler, concept artist, musician, writer, marketeer, community manager,...
Something I've noticed about software dev vs game design: software is better (easier to read, understand, maintain, etc) when you have clean, separated modules. Game design is better (more fun) when everything is connected (eg in an fps, everything relates to gunplay, damage systems, environmental destruction, in a building game, everything relates to building, the building ui, inventory, resources etc). I think this mismatch shows up in game code.
FWIW sometimes when making games it helps to have everything in one place, even though later you may want to split things up for later projects :-P so sometimes you get big balls of mud with lots of checks everywhere for the actions you want.
As an example, most of the gameplay logic for Post Apocalyptic Petra[0] (a 3D platformer/adventure/riddle solving game i made for an MSDOS game jam a few years ago) is in the entity class for the player character[1] - including things like collisions, etc :-P. It is a bit hacky, but it works.
Though if i ever made that sequel i wanted to where i want to have an extra character following you around, i'd need to move a bunch of things to other more generic places so that they can be shared by both the player character and the AI.
Solo dev also just doesn't need a lot of abstraction. You're adding a lot of noise for what sounds like zero benefit to developer and especially not the player.
Game dev is also a lot more agile than other projects. Solidifying structure is just more to rip up later unless you really know what you want to ship.
You think that's bad. Wait until you see some non game developer coders (I'm being sarcastic of course. The comment I'm replying to is incredibly naive).
It's also because gamedev is very highly iterative, so if you're abstracting stuff you end up throwing away your abstractions when things inevitably change. And that costs more than just changing some if statements.
I started using .Net with 2.0, learned the api and framework and was very happy with all the functions. i never had the need to switch to the more idiomatic idoms. Sometimes on the go i picked up some new tricks and adopted some new types. Form me a good programmer knows how to avoid cognitive overload and how things work under the hood. Why use LINQ when i can use a foreach or a hashmap. Most of the time people don't understand how i get so much performance out of C#, then i tell them KISS. And know your type system.
Some nice tips in here ([field: SerializeField] for example) - but as others note, it's not about modern code, as many of these features have been available for an embarrassingly long time. It's always felt to me that there's some fundamental friction between idiomatic C# and Unity.
I remember, after reading about new features C# 8.0, someone wrote that C# 9 would write all your code for you, and that C# 10 would just mail you a check every month. How the times have changed...
game development for steam and mobile audiences became so inaccessible due to Unity, iOS and Android's complexity & evolution, a lot of "game" development was programmers engaging in an intellectually curious but otherwise meaningless engine-twiddling circle jerk of sorts. people with good game ideas were not necessarily able to tackle the complexity of those engines - and, based on how bad the games on Roblox are, they aren't using alternative platforms either. they just have had to spend an incredible amount of time to develop something. for everyone else, there is already basically zero audience for most games, so we're going to have heard about, "I made a Rust ECS game engine that runs on a Wiimote" or whatever, because there's an audience for blog posts on hacker news.
then claude code swung everything back in the other direction. things are accessible again. does any of what the article says matter anymore? games are the ultimate, "if it looks good and works correctly, it is good" software product, nobody is going to care if you use [field: SerializeField] or records or whatever.
so yeah, will Claude Opus 6 mail you a check every month? who even needs Unity?
I am not a game developer (I've made a few in the past but didn't use any frameworks besides direct libsdl calls), but if this article rings true for anyone in that field I'm a bit surprised things as basic as properties, structs and tuples are "not used" by unity devs, this is some basic stuff that has been supported even by Mono for decades. Just basic syntactic sugar though, so not a big deal either way. Just surprising to me at least.
c# 14 added field backed properties where you dont need that `_health` in the first place you just write `public int Health { get => field; set => field = Mathf.Clamp(value, 0, 100)`. that way you never accidentally use the internal field without the checks. problem is unity still stuck on c# 9.0 so it might be years before you can actually use this in a game
Unity's C# has always felt like C#'s mentally challenged cousin. C-not-so-sharp. The custom == convinced me that allowing operator overloading on built-in operators is one big mistake.
One take on it is that yes, the single dot operator was an ancient mistake which is why so many programming language features are about making it smarter. Properties as mentioned in this article are an ancient way to fake the dot operator into a "field" but actually make method calls. Modern C# also picked up the question dot operator (?.) for safer null traversal and the exclamation dot operator (!. aka the "damnit operator" or "I think I know what I'm doing operator") for even less safe null traversal.
For those wondering, Unity overloaded the == operator in two specific situations, such that obj == null will return true even though obj is not actually null. More details on this archived blogpost: https://web.archive.org/web/20140519040926/https://blogs.uni...
I prefer Godot over Unity honestly. Not just because the engine feels better but because it's accessible, which is what matters to me. Unity isn't and probably never will be, so meh. Sure you can make accessible games in it but the editor itself isn't accessible so it kinda defeats the point of being able to make accessible games in it in the first place. And don't even get me started on Unity's licensing model. Godot's superior C# support is, IMO, just a cherry on top.
Iirc Godot is taking the approach from both sides however and also working on a libgodot which will allow a "bring your own runtime" which I'm much more interested in than the integrated "environments" that are Unity and Godot today. I'm likely in the minority though as they only started making a library export after the all in one environment was stable enough...
Godot is nowhere near to something like C# HPC, Jobs and Burst. And I’m afraid even GDExtension can’t help with that.
At least not with Godot’s scene structure which prioritises simplicity over performance.
I'm a very experienced Unity C# programmer, and I certainly don't equate "good" with using all the new fancy features of a language.
Fancy features are less maintainable imo. Less programmers will know about them and they're less likely to have equivalents in other languages.
Making something more exotic / confusing / hard to parse is defo not worth saving a few lines of code.. I'd much rather see a longer function using absolute bog standard elements of the language (and thus being clear, easy to comprehend for everyone, easy to modify at any point) rather than a super short, super "elegant", super "clever" solution.
Languages that seem to indefinitely grow more features over time (like c++, c#, rust, etc) evitably become bucketed by epochs unless the consuming application code also operate across the same time scales. Feature deprecations tend to go hand in hand with newer features, leaving you with basically "multiple sublanguages" in a supposed single language, exacerbating fragmentation of the community. I don't want to have the mental load of contextually understanding "which" sublanguages I need to care about depending on the year a consuming application was written. This is why I tend not to reach for new fangled features and stay with the core runtime stuff in evergreen langs.
I don't know... I feel like a lot of these features do increase developer intent without breaking muscle memory. Properties are got and set like fields. Record types feel like classes with restrictions so the linter can warn you about broken assumptions. etc etc.
Others increase readability. A LINQ statement is a lot easier to parse than a long block inside a foreach.
New doesn't mean good but a lot of these are new and good.
What C# version does Unity currently support? 2024 I chose Godot over Unity due to its better C# support and I can’t say that I came to regret my decision.
C# 9, but with some hacks you can bump it up to C# 10 - actually works and surprisingly stable. Can't wait for them to finally migrate to CoreCLR, though.
Iirc the lineage of their c# came from Mono, then diverged a bit over time. Hopefully they can leave that baggage behind and just use the newer .net core, if they're not already that is... Disclaimer: I haven't looked in half a decade
idk, maybe the youtube algorithm just gets people to click on the video, what do they care why a video is clicked on as long as it generates revenue. so when they talk about videos being useful, take that to the bank.
Game developers are not paid to be good developers. They're paid to be young, naive, and easily brow-beat into working unpaid overtime.
I think one of my biggest problems with Unity is that it enabled a massive market of me-too "business men" who "employ" unpaid and underpaid interns to hack together asset-store-ware they then dump on the app stores. When a gem game stutters, people blame their crappy phones rather than the company who probably stiffed its developers.
I've seen a lot of my friends do this constant churn of signing up for the next game shop that will hire them. Places that throw many, many red flags the second you even walk in the door. They work hard to get a game done on a budget 1/10th what it should be, the game ends up being a flop, and they never get a chance to grow their portfolio or skills to eventually get a better job.
This isn't something you can lay at the feet of Unity Technologies, but I do think it is a reason to avoid Unity: the job ecosystem is just awful.
> The Unity engine has evolved a lot in modern days, but I noticed a trend where Unity developers are still using "outdated" techniques when writing their C# code.
Some years ago I tried to get into C# + Mono. Eventually I opted for Java instead, for many reasons; I'll skip that here.
C# is very strange to me. In a way I feel that C# belongs like Java in the same "post C++" family; C kind of paved the way, C++ was messy and powerful, so Java and C# would be more "managable". But I never got into C#. Java is not a pretty language, it is also quite boring, but modern Java is somewhat acceptable - you get the job done. And it is not an extremely difficult language either for the most part, just with an addiction on pointless verbosity. C# is ... strange though. TIOBE has it ranked #5 right below Java, so there must be many C# users, but I don't get to see them really in the Linux ecosystem. So where are these people all? Using Windows only? When the question is "most developers don't use feature xyz", do all of them actually KNOW these features? You can still find many java tutorial where people use archaic ways to, for instance, iterate over a collection. Perhaps it is similar to the C# ecosystem, people are slow to adopt. Or, and this may also be a reason, people could have moved to other languages. This may not be a huge percentage, but you see that some languages suddenly struggle with old devs and failing to get new devs (ruby is in this problem right now; it may overcome it but right now it is sinking hard, even though I would reason that the language is, for the most part, better than it was in, say, 2010).
C# has had the reputation of not being viable for Linux for a long time.
Therefore, the people already on Linux didn't have a reason to use it or even try it.
If you're already doing stuff in other languages it's hardly worth it to switch to C#.
I personally use it quite a lot - but I came as a windows user writing all my utilities in C#.
Also, afaik C# is mostly used in corporate environments that don't open-source their projects. You're unlikely to hear from it unless you're working on it for this very reason.
Mono was in a usable state on Linux for literal decades before becoming official and integrated into what is core today, that is unless you needed windows forms, which much like MSFT UI frameworks today had multiple failed attempts spanning those same decades...
We attempted a move to mono for backend web services maybe 3 years before .NET Core released, and it was a complete no-go. 10x reduction in performance on the same hardware.
This wasn't specific to our workload either. I was big into the game Terraria at the time, and I saw similarly poor performance when running it's game server under mono vs .NET 4.x
While some of the mono toolchain was integrated into .NET Core, CoreCLR was a rewrite and immediately solved this problem.
A lot of C#'s reputation for not being viable for Linux came from the other direction and a lot of FUD against Mono. There were a lot of great Linux apps that were Linux first and/or Linux only (often using Gtk# as UI framework of choice) like Banshee and Tomboy that also had brief bundling as out-of-the-box Gnome apps in a couple of Linux distros before anti-Mono backlash got them removed.
Also, yeah today Linux support is officially maintained in modern .NET and many corporate environments are quietly using Linux servers and Linux docker containers every day to run their (closed source) projects. Linux support is one of the things that has saved companies money in running .NET, so there's a lot of weird quiet loyalty to it just from a cost cutting standpoint. But you don't hear a lot about that given the closed-source/proprietary nature of those projects. That's why it is sometimes referred to as "dark matter development" from "dark matter developers", a lot of it is out there, a lot of it doesn't get noticed in HN comments and places like that, it's all just quietly chugging along and doesn't seem to impact the overall reputation of the platform.
C# has a unique powerful position in the video game space. In almost every other niche, there are better (or just trendier) solutions, but C# is the only major language that actually gives you a combination of features that are important in video games:
- Dynamic runtime with loose coupling and hot reload of code - extremely useful during development.
- Value types. You don't want every Vector4 to be heap allocated when you're doing 3D graphics, because that's going to be absolutely unusable.
- Access to a relatively low-level substrate for basically-native performance when needed - extremely useful when you're trying to actually ship something that runs well.
Taken in isolation, C# isn't best in class for any of them, but no other language offers all three, especially not if you also want things like a really good debugger and great IDE tools.
To my knowledge, Java has none of these features (yet), and they aren't really important in a lot of the areas where Java is popular. But this is why C# in particular is very strong in the video games niche.
The second bullet is a headline goal of Project Valhalla, however it is unlikely to be delivered in quite the way that a C# (or Go or Rust etc.) developer might expect. The ideal version would allow any object with purely value semantics [1] to be eligible for heap flattening [2] and/or scalarization [3], but in experimental builds that are currently available, the objects must be from a class marked with the "value" qualifier; importantly, this is considered an optimization and not a guarantee. More details: https://openjdk.org/projects/valhalla/value-objects
The third bullet (IIUC) is addressed with the Foreign Function & Memory API, though I'll admit what I've played around with so far is not nearly as slick as P/Invoke. See e.g. https://openjdk.org/jeps/454
[1] value semantics means: the object is never on either side of an == or != comparison; the equals and hashCode methods are never called, or are overridden and their implementation doesn't rely on object identity; no methods are marked synchronized and the object is never the target of a synchronized block; the wait, notify, and notifyAll methods are never called; the finalize method is not overridden and no cleaner is registered for the object; no phantom or weak references are taken of the object; and probably some other things I can't think of
[2] heap flattening means that an object's representation when stored in another object's field or in an array is reduced to just the object's own fields, removing the overhead from storing references to its class and monitor lock
[3] scalarization means that an object's fields would be stored directly on the stack and passed directly through registers
The third bullet is also presumably referring to C#'s ancient wider support for unsafe { } blocks for low level pointer math as well as the modern tools Span<T> and Memory<T> which are GC-safe low level memory management/access/pointer math tools in modern .NET. Span<T>/Memory<T> is a bit like a modest partial implementation of Rust's borrowing mechanics without changing a lot of how .NET's stack and heap work or compromising as much on .NET's bounds checking guarantees through an interesting dance of C# compiler smarts and .NET JIT smarts.
The FFM API actually does cover a lot of the same ground, albeit with far worse ergonomics IMO. To wit,
- There is no unsafe block, instead certain operations are "restricted", which currently causes them to emit warnings that can be suppressed on a per-module basis; it seems the warnings will turn into exceptions in the future
- There is no "fixed" statement and frankly nothing like it all, native code is just not allowed to access managed memory period; instead, you set up an arena to be shared between managed and native code
- MemorySegment is kinda like Memory<T>/Span<T> but harder to actually use because Java's type-erased generics are useless here
- Setting up a MemoryLayout to describe a struct is just not as nice as slapping layout attributes on an actual struct
- Working with VarHandle is just way more verbose than working with pointers
> - There is no unsafe block, instead certain operations are "restricted", which currently causes them to emit warnings that can be suppressed on a per-module basis; it seems the warnings will turn into exceptions in the future
Which sounds funny because C# effectively has gone the other direction. .NET's Code Access Security (CAS) used to heavily penalize unsafe blocks (and unchecked blocks, another relative that C# has that I don't think has a direct Java equivalent), limiting how libraries could use such blocks without extra mandatory code signing and permissions, throwing all sorts of weird runtime exceptions in CAS environments with slightly wrong permissions. CAS is mostly gone today so most C# developers only ever really experience compiler warnings and warnings-as-errors when trying to use unsafe (and/or unchecked) blocks. More libraries can use it for low level things than used to. (But also fewer libraries need to now than used to, thanks to Memory<T>/Span<T>.)
> There is no "fixed" statement and frankly nothing like it all, native code is just not allowed to access managed memory period; instead, you set up an arena to be shared between managed and native code
Yeah, this seems to be an area that .NET has a lot of strengths in. Not just the fixed keyword, but also a direct API for GC pinning/unpinning/locking and many sorts of "Unsafe Marshalling" tools to provide direct access to pointers into managed memory for native code. (Named "Unsafe" in this case because they warrant careful consideration before using them, not because they rely on unsafe blocks of code.)
> MemorySegment is kinda like Memory<T>/Span<T> but harder to actually use because Java's type-erased generics are useless here
It's the ease of use that really makes Memory<T>/Span<T> shine. It's a lot more generally useful throughout the .NET ecosystem (beyond just "foreign function interfaces") to the point where a large swathe of the BCL (Base Class Library; standard library) uses Span<T> in one fashion or another for easy performance improvements (especially with the C# compiler quietly preferring Span<T>/ReadOnlySpan<T> overloads of functions over almost any other data type, when available). Span<T> has been a "quiet performance revolution" under the hood of a lot of core libraries in .NET, especially just about anything involving string searching, parsing, or manipulation. Almost none of those gains have anything to do with calling into native code and many of those performance gains have also been achieved by eliminating native code (and the overhead of transitions to/from it) by moving performance-optimized algorithms that were easier to do unsafely in native code into "safe" C#.
It's really cool what has been going on with Span<T>. It's really wild some of the micro-benchmarks of before/after Span<T> migrations.
Related to the overall topic, it's said Span<T> is one of the reasons Unity wants to push faster to modern .NET, but Unity still has a ways to go to where it uses enough of the .NET coreclr memory model to take real advantage of it.
There is a huge amount of closed source, ecommerce shops that use C#. Its popular in game dev too but TIOBE is probably attributable to the ecommerce.
If you know Java, you should give C# a try. Its a slicker Java with some good decisions that actually make it viable for a lot of things Java struggles at, like better interop with pointers and things like native UI (Maybe Loom will eventually overtake async/await for UI dev but not quite yet).
It works very well on many more use cases than it used to. .NET has followed with the Linux servers in the cloud industry reality and is very stable.
We are deploying .NET (Core) on Linux since soon 10 years. With hundreds of devs and dozens of services on thousands of servers.
We have not touched a Windows server in years.
In 2026 .NET is deployed like anything else into Linux containers and Lambdas. Obviously there are still people who love their ui based servers but that is because of that and not because of .NET.
Adoption of new features is gradually and explorative, obviously in an enterprise everything is on a LTS 2-3 back but adoption after that delay is rather quick to my observation.
.NET like Java have this "it is there and we extend it. And the new platform is also .NET because you have your dozens of devs already"
> I don't get to see them really in the Linux ecosystem
Sounds like you're just in a part of the ecosystem that doesn't touch C#. Aside from gamedev, C# is mostly a back-end web language, and almost all new builds are going to deploy to some Linux based serverless offering or something like Kubernetes. At my work, we're a mix of Python/Typescript/C# deploying to Google Kubernetes Engine and Cloud Run.
As for the language, your parallels to Java are accurate but outdated. Older versions of C# were very very similar to Java, but C# progressed when Java didn't. For better or worse (depending on your tastes), C# is more comparable to Kotlin or Swift these days than Java.
That's to say nothing of C#'s contributions to other programming languages; async/await and unsafe{} blocks were both first seen in C# before being adopted elsewhere.
Doesn't hn clean article titles? This is a classic click baity one. Most of these are just basic features. Some are unused for good reason.
- Property: the inspector doesn't call your getter or setter. I do use them still because i like to centralize my validation logic. But need custom machinery to make them behave consistently.
- Tuple: well-known. Good but only in moderation.
- Linq: people avoid it due to allocations, not runtime. While it is possible to avoid dynamic alloc, it is not obvious and best avoided. Also the point about the linq syntax being "cleaner" is debatable.
- Record: good. Lesser known as it's the newest in the article. No footguns like the other.
While it is nice that this is human written, the seo format is nearly as annoying as those ai articles.
As a primarily C# developer who has done some game engine work, I recently gave Godot a go for licensing reasons. Apart from some quirkiness I'm fairly impressed. Much nicer C# support compared to Unity. I've done a fair bit of Unreal C++ but to be honest unless you really need the performance that's just too much hard work.
Having said that getting the code working properly with a nice 3D UI is my priority, not having a slick game with some code doing some mundane stuff in the background.
I can't comment on Godot or Unity which both use primarily C#.
Unreal engine which uses C++ primarily, has the problem that it's a humongous mostly legacy code macro heavy system .
If anything being proficient in C++ before you start is harmful because of the puckering of orifices when you hear about it's mostly quirky powerful macros all the way down.
Never worked with it myself but I've always heard the people who do describe it as Unreal c++ because to them it's completely different than regular c++ and this must be one of the reasons why
I agree. I came to Unreal with only a basic level of C++. Having Unreal handle memory management for you was useful, but I can imagine the chaos an Unreal-only C++ developer might cause when unleashed on another C++ codebase.
I don't want to design anything in any UI.
I want to use a game engine as a library.
I'm curious, as a primarily C# developer, how do you feel about Godot, in this respect?
> I don't want to design anything in any UI.
I had the same need and found Raylib_cs suit very well for that
It's 100% completely usable for that purpose. To the point where I'm internally using Godot as a backend for my engine, using it for rendering/system events.
It's also really good for a hybrid setup where you use their editor to design scenes, and then you can load/instantiate the scenes at runtime how/when you want. Or, just programmatically creates scenes yourself from code. You can really do whatever you want.
Fans of LINQ may enjoy ZLinq[0], which is a less versatile but much more performant way to write LINQ-like queries. I certainly use a lot of (Z)Linq in my code; the performance tradeoff is just fine for one-off initialization, UI code, editor tooling, etc.
[0]: https://github.com/Cysharp/ZLinq
Zero allocation impl? very cool! Arguably, the original runtime should have done this in the first place given the limitations/tradeoffs aren't so bad, but I guess that ship sailed 20 years ago (man that hurts)
The modern .NET runtime can get devirtualize interface calls and eliminate temporary object allocations in some scenarios. It's a bit of a black box - who knows when it actually works? - but still, it's a nice boost here and there.
Most .NET projects that Linq originally targeted ran in so called "Server" and/or "Workstation" GCs (.NET has had very generic public names for its GCs for a long time which were also somewhat misnomers because even some Desktop apps would run in "Server" GC and it was possible for vice versa [0]) where allocations were cheap, garbage collection was relatively cheap (because both GCs were multi-generational, had strong [but different] tuning for their generations, etc).
Unity inherited a much simpler Boehm GC from Mono. Under a (single generation) Boehm GC allocations are a bit more expensive and garbage collection sometimes a lot more expensive. (A Boehm GC was much easier for C++ engine code to understand/interact with, which is also part of why the .NET modernization project for Unity got so complicated and still has such a ways to go left.)
[0] Fun aside: in fact, modern docker advice for .NET is to switch "server applications" to use "Workstation GC" if you need to stack multiple containers on the same host because of differences in expected memory usage.
A lot of game devs are terrible programmers. A friend of mine 10 years ago asked me for help with his Unity project. He is not a tech savvy person but we both took programming in high school, enough for him to make small games with a lot of tutorials and stack overflow.
His codebase was horrible, a lot of logic that I would have already though of abstracting away. For example saving dialogs on json files and the conditions for that dialog to trigger for that NPC as some sort of finite state machine that can be represented with a series of sequential flags. He had a single file that was about 15k lines full of `if (condition && condition) || (condition && condition)` statements. He didn't seem to see the issue, it just worked.
That's when I understood some people just care about game development and doing cool stuff and don't care at all about programming, good practices or structured code. And that's perfectly fine.
Most indie game dev projects start as some small weekend project just to feel things out. Then it starts to become fun so we work on it another week. Then after a couple months we start to think that maybe the game has potential. Then we're 5 years into a project and have no clue how we got there. It becomes a giant jenga tower where moving any one block can completely collapse the whole project, so we learn the hard way that nobody should ever refactor. Pretty much the only people who do refactor end up restarting their project from scratch, getting frustrated because they can't capture their original feel of the game, then ultimately abandon the project entirely.
And for professional game dev projects, it's all built on a foundation of some scrappy little indie project from decades ago.
Some industries are all about making their code public and making it super clean and polished as a point of pride. Games, like movies and sausage, are disgusting to see behind the scenes. They're just piles of scraps and weird tricks that look great unless you get down and examine it too closely. And most people aren't looking that closely, so wasting that time and effort is pointless.
I’ve met plenty of these types in enterprise jobs too.
Since it's a multi-discipline craft it's hard to get good at every aspect for indie development, focusing most of the effort on one or two aspects. I think the programming aspect for indie games typically matters very little unless it hurts performance or causes bugs, and the things the user interacts with end up mattering a lot more.
Every web developer I've met has specialised in one area or another, even if they claim the title of "Full Stack".
Problem is that this kind of code often is brittle, full of bugs and unhandled edge cases, and evolution and maintenance is horror. But if it’s all you know you might never question it.
Often but not always, and if they're a solo developer then maintenance might not be too bad as they might be able to keep all the logic in their head. I'm not advocating for that kind of approach, but if it lets people focus on things that the player will actually notice like the gameplay, graphics, sound, story or art then hey, what's a little shortcut?
A game is finished at some point. You might not need to evolve the code any further. Why optimize for a use-case that rarely applies to indie-games?
If you’re a solo developer, if it works.
I’ve seen worse in enterprise shops and then I’ve gotten into nasty arguments with people who don’t care about programming. They can’t be wrong.
C# is a high level language that can handle a degree of sloppy programming.
I was working on a small tool yesterday. It was easier to vibe code it from scratch in C# than to modify an existing Rust project.
The only weird part is VS Code Copilot couldn’t figure out how to build it via the dotnet cli and I had to install VS Studio. After that everything was fine.
I would say that C# is "less colored" than Rust. Handling a moved argument, a borrowed argument, a copied argument is different in Rust, you have to think upfront about memory lifetimes. If you are wrong the first time, changing the ownership is not a simple localized refactoring.
To be 100% fair, it’s much easier for llms to start new projects and Rust is simply a more difficult language.
I have what I need working in C#, and as a C# developer I actually understand what’s going on.
The only downside is now instead of having a portable Rust project, I have something which heavily leans into Windows APIs.
I assume with high level languages some smart people figured out all the memory stuff.
This goes for devs in all industries, it's not a problem unique to games.
If anything, the most competent developers in terms of getting the most performance out of hardware are game developers.
I think as a solo developer there's actually a good argument for increasing code density and coupling (things which in large multi developer projects are seen as spaghetti), as it can help you keep a lot of that code in mental and visual context at one time.
It loses flexibility and readability for others, but you don't usually have enough time to concern yourself with such flexibility if you're working on a project by yourself, and you're not concerned about onboarding other developers and having them understand your code. The upshot is then that as a single person "bad code" is often highly effective code, and "clean code" is expensive code that buys you a lot of stuff you don't need or want.
I say this as a boring enterprise developer who at work is highly concerned with appropriate abstractions etc. imo there's no universally good approach, what is optimal is context dependent. Although there are some core features of code like consistency and strong conventions which are fairly universally helpful, this represents a small fraction of best practices.
This is pretty anti-thetical to most good practices but the older and more experienced I get the more(13 years as a C# dev) I think copy & pasting sections of code is wayyyyyy more appropriate than extracting into a method/class/library or other forms of abstraction.
Everything starts out with good intentions when someone comes along and says “hey you could make that an abstraction” and I just clench my jaw because I’ve seen that happen so much and then that simple clean abstraction eventually ends up being a horrible 1000 line monster that barely anyone understands and no one wants to change.
I agree with everything except for it being anti-thetical to good practice. I have noticed a lot of experienced devs agree with that sentiment.
It has been a pretty common trend for the last few years of people breaking out of the “OOP style programming” and practices they were taught at university. I am not saying avoiding things like over abstraction is new, but I do think there is a newer generation of programmers who have been taught and warned about drawbacks from practices like that.
Similarly, my anecdotal experience tells me more newer game devs are aware of basic memory practices being better than overly complex OOP code. Think flat arrays and simple cache alignment over something abstract and over engineered
KISS is also good practice.
100% this. All the abstractions and OOP stuff make you end up with a codebase where half the code doesn’t actually DO anything in the product itself, it just connects to other code! It also becomes impossible to follow the flow of execution because it passes through dozens of files and layers of abstraction.
Gamedev at a high level is similar in spirit to distributed systems or compilers in that it's intensely multi-disciplinary with hard constraints. In gamedev you have a main thread that you're always sweating about submilliseconds, especially with worker threads interfering with it. Processor, memory, bus characteristics, GPUs, cooling, etc. all matter.
This leads to e.g., everyday AoS vs. SoA, pooling and burst compile, to the classic fast inverse square root because of hardware at the time. Relentless optimization of hot paths produces code that's about performance, not abstraction. Then there's shaders, which are effectively a different programming model targeting different hardware entirely. Now add support for multiple operating systems, consoles, whatever. The list goes on.
Now, all of that doesn't obviate the value of design and craft, so I don't agree that it's "perfectly fine". There are plenty of programmers weak on these two axes in most any domain, but it's worth noting gamedev is a special case that significantly distorts what good code, or at least good-enough code, looks like.
Games start as little experiments and end up as Frankensteins. This is their nature; you're more sculpting a thing by building it and experiencing it rather than designing it a priori with systemic elegance in mind.
And that first bit only really matters if you're making a fast-paced game with sophisticated graphics. Most indie games can get away with horribly inefficient code.
I don’t see the issue. Real software problems are when the logical model is insufficient for the given problem.
To be a bit more charitable: I'd say that generally games involve a lot more special-casing than most code, and more planned out scripts (in the movie sense) of things happening, which tend to be antithetical to good coding practice, and encourage spaghetti, which begets more. In my experience, games that are procedural tend to be much cleaner code-wise, because they tend to fit the model of cleaner code better.
I think game engine tooling tends to encourage bad code too, lots of game engine make it hard to do everything in code, rather things are special cased through UIs or magic in the engine, which means you often can't use all the normal language features, and have to do things in awkward ways to fit the tooling.
Of course, this varies a lot by engine.
IME you have so little reuse, and ship on a fixed schedule regardless of code quality & bugs this really isn't as critical as software built with the intent of lasting a long time & evolving. The games I've worked on (in hindsight) feel a lot like "vibe-coded without AI".
It's an indication that programming languages are the wrong abstraction for creative work
The other day a professional gamedev was arguing with me that's Transform.GetChild to get a reference to a component was not a bad practice. His argument is that it's what every other firms in his area is also doing. I do hope that was not the truth and he was just trying to win the argument.
For solo hobby dev it is a lot more acceptable. After all they're also terrible 3d modeler, concept artist, musician, writer, marketeer, community manager,...
I mean it’s not that bad of a practice, it depends on the context.
Something I've noticed about software dev vs game design: software is better (easier to read, understand, maintain, etc) when you have clean, separated modules. Game design is better (more fun) when everything is connected (eg in an fps, everything relates to gunplay, damage systems, environmental destruction, in a building game, everything relates to building, the building ui, inventory, resources etc). I think this mismatch shows up in game code.
Combine that with actual hard performance constraints, and if you're a "normal" software dev casually browsing some game code, it can be shocking (eg Celeste movement code: https://github.com/NoelFB/Celeste/blob/master/Source/Player/...).
FWIW sometimes when making games it helps to have everything in one place, even though later you may want to split things up for later projects :-P so sometimes you get big balls of mud with lots of checks everywhere for the actions you want.
As an example, most of the gameplay logic for Post Apocalyptic Petra[0] (a 3D platformer/adventure/riddle solving game i made for an MSDOS game jam a few years ago) is in the entity class for the player character[1] - including things like collisions, etc :-P. It is a bit hacky, but it works.
Though if i ever made that sequel i wanted to where i want to have an extra character following you around, i'd need to move a bunch of things to other more generic places so that they can be shared by both the player character and the AI.
[0] https://bad-sector.itch.io/post-apocalyptic-petra
[1] https://codeberg.org/badsector/PetraEngine/src/tag/pap-0.99o...
Solo dev also just doesn't need a lot of abstraction. You're adding a lot of noise for what sounds like zero benefit to developer and especially not the player.
Game dev is also a lot more agile than other projects. Solidifying structure is just more to rip up later unless you really know what you want to ship.
You think that's bad. Wait until you see some non game developer coders (I'm being sarcastic of course. The comment I'm replying to is incredibly naive).
It's also because gamedev is very highly iterative, so if you're abstracting stuff you end up throwing away your abstractions when things inevitably change. And that costs more than just changing some if statements.
I started using .Net with 2.0, learned the api and framework and was very happy with all the functions. i never had the need to switch to the more idiomatic idoms. Sometimes on the go i picked up some new tricks and adopted some new types. Form me a good programmer knows how to avoid cognitive overload and how things work under the hood. Why use LINQ when i can use a foreach or a hashmap. Most of the time people don't understand how i get so much performance out of C#, then i tell them KISS. And know your type system.
Some nice tips in here ([field: SerializeField] for example) - but as others note, it's not about modern code, as many of these features have been available for an embarrassingly long time. It's always felt to me that there's some fundamental friction between idiomatic C# and Unity.
I remember, after reading about new features C# 8.0, someone wrote that C# 9 would write all your code for you, and that C# 10 would just mail you a check every month. How the times have changed...
game development for steam and mobile audiences became so inaccessible due to Unity, iOS and Android's complexity & evolution, a lot of "game" development was programmers engaging in an intellectually curious but otherwise meaningless engine-twiddling circle jerk of sorts. people with good game ideas were not necessarily able to tackle the complexity of those engines - and, based on how bad the games on Roblox are, they aren't using alternative platforms either. they just have had to spend an incredible amount of time to develop something. for everyone else, there is already basically zero audience for most games, so we're going to have heard about, "I made a Rust ECS game engine that runs on a Wiimote" or whatever, because there's an audience for blog posts on hacker news.
then claude code swung everything back in the other direction. things are accessible again. does any of what the article says matter anymore? games are the ultimate, "if it looks good and works correctly, it is good" software product, nobody is going to care if you use [field: SerializeField] or records or whatever.
so yeah, will Claude Opus 6 mail you a check every month? who even needs Unity?
I am not a game developer (I've made a few in the past but didn't use any frameworks besides direct libsdl calls), but if this article rings true for anyone in that field I'm a bit surprised things as basic as properties, structs and tuples are "not used" by unity devs, this is some basic stuff that has been supported even by Mono for decades. Just basic syntactic sugar though, so not a big deal either way. Just surprising to me at least.
c# 14 added field backed properties where you dont need that `_health` in the first place you just write `public int Health { get => field; set => field = Mathf.Clamp(value, 0, 100)`. that way you never accidentally use the internal field without the checks. problem is unity still stuck on c# 9.0 so it might be years before you can actually use this in a game
I code modern C# during the day and Unity for fun and I found this a really good roundup.
It's also worth noting that Unity does all sorts of OO-breaking filth before passing to IL2CPP.
Unity's C# has always felt like C#'s mentally challenged cousin. C-not-so-sharp. The custom == convinced me that allowing operator overloading on built-in operators is one big mistake.
Many tools can be misused. Object.Member can throw a NRE, is it a big mistake to have the dot operator?
It's such a weird question.
Yes, "dot operator can throw a NRE" is of course a big mistake. A billion-dollar mistake, you can even say.
No, I'm not asking if "dot operator can throw a NRE" is a mistake; I'm asking if the dot operator, the ability to access members at all, is a mistake.
One take on it is that yes, the single dot operator was an ancient mistake which is why so many programming language features are about making it smarter. Properties as mentioned in this article are an ancient way to fake the dot operator into a "field" but actually make method calls. Modern C# also picked up the question dot operator (?.) for safer null traversal and the exclamation dot operator (!. aka the "damnit operator" or "I think I know what I'm doing operator") for even less safe null traversal.
For those wondering, Unity overloaded the == operator in two specific situations, such that obj == null will return true even though obj is not actually null. More details on this archived blogpost: https://web.archive.org/web/20140519040926/https://blogs.uni...
I prefer Godot over Unity honestly. Not just because the engine feels better but because it's accessible, which is what matters to me. Unity isn't and probably never will be, so meh. Sure you can make accessible games in it but the editor itself isn't accessible so it kinda defeats the point of being able to make accessible games in it in the first place. And don't even get me started on Unity's licensing model. Godot's superior C# support is, IMO, just a cherry on top.
Looking forward to when Unity will migrate to CoreCLR. Soon!
https://www.youtube.com/watch?v=_t6xVfrmEWU
That soon is like a decade in the making.
And with many folks going into alternatives like Godot, it means C# ends up losing the mindshare it got.
Yes, you can use C# with Godot, but most folks end up with GDScript, or GDextension.
Iirc Godot is taking the approach from both sides however and also working on a libgodot which will allow a "bring your own runtime" which I'm much more interested in than the integrated "environments" that are Unity and Godot today. I'm likely in the minority though as they only started making a library export after the all in one environment was stable enough...
Godot is nowhere near to something like C# HPC, Jobs and Burst. And I’m afraid even GDExtension can’t help with that. At least not with Godot’s scene structure which prioritises simplicity over performance.
I'm a very experienced Unity C# programmer, and I certainly don't equate "good" with using all the new fancy features of a language.
Fancy features are less maintainable imo. Less programmers will know about them and they're less likely to have equivalents in other languages.
Making something more exotic / confusing / hard to parse is defo not worth saving a few lines of code.. I'd much rather see a longer function using absolute bog standard elements of the language (and thus being clear, easy to comprehend for everyone, easy to modify at any point) rather than a super short, super "elegant", super "clever" solution.
Languages that seem to indefinitely grow more features over time (like c++, c#, rust, etc) evitably become bucketed by epochs unless the consuming application code also operate across the same time scales. Feature deprecations tend to go hand in hand with newer features, leaving you with basically "multiple sublanguages" in a supposed single language, exacerbating fragmentation of the community. I don't want to have the mental load of contextually understanding "which" sublanguages I need to care about depending on the year a consuming application was written. This is why I tend not to reach for new fangled features and stay with the core runtime stuff in evergreen langs.
I don't know... I feel like a lot of these features do increase developer intent without breaking muscle memory. Properties are got and set like fields. Record types feel like classes with restrictions so the linter can warn you about broken assumptions. etc etc.
Others increase readability. A LINQ statement is a lot easier to parse than a long block inside a foreach.
New doesn't mean good but a lot of these are new and good.
What C# version does Unity currently support? 2024 I chose Godot over Unity due to its better C# support and I can’t say that I came to regret my decision.
C# 9, but with some hacks you can bump it up to C# 10 - actually works and surprisingly stable. Can't wait for them to finally migrate to CoreCLR, though.
I got out of doing Unity development 7 years ago because I was tired of waiting for them to migrate to CoreCLR (among many other reasons).
Unfortunately the way they managed to stick with MonoRuntime and Burst, kind of made more harm than good, regarding C# adoption on the games industry.
Many issues people associate with C#, are actually only relevant in Unity, because of this.
Iirc the lineage of their c# came from Mono, then diverged a bit over time. Hopefully they can leave that baggage behind and just use the newer .net core, if they're not already that is... Disclaimer: I haven't looked in half a decade
idk, maybe the youtube algorithm just gets people to click on the video, what do they care why a video is clicked on as long as it generates revenue. so when they talk about videos being useful, take that to the bank.
Game developers are not paid to be good developers. They're paid to be young, naive, and easily brow-beat into working unpaid overtime.
I think one of my biggest problems with Unity is that it enabled a massive market of me-too "business men" who "employ" unpaid and underpaid interns to hack together asset-store-ware they then dump on the app stores. When a gem game stutters, people blame their crappy phones rather than the company who probably stiffed its developers.
I've seen a lot of my friends do this constant churn of signing up for the next game shop that will hire them. Places that throw many, many red flags the second you even walk in the door. They work hard to get a game done on a budget 1/10th what it should be, the game ends up being a flop, and they never get a chance to grow their portfolio or skills to eventually get a better job.
This isn't something you can lay at the feet of Unity Technologies, but I do think it is a reason to avoid Unity: the job ecosystem is just awful.
Why wouldn't a released game grow their portfolio?
> The Unity engine has evolved a lot in modern days, but I noticed a trend where Unity developers are still using "outdated" techniques when writing their C# code.
Some years ago I tried to get into C# + Mono. Eventually I opted for Java instead, for many reasons; I'll skip that here.
C# is very strange to me. In a way I feel that C# belongs like Java in the same "post C++" family; C kind of paved the way, C++ was messy and powerful, so Java and C# would be more "managable". But I never got into C#. Java is not a pretty language, it is also quite boring, but modern Java is somewhat acceptable - you get the job done. And it is not an extremely difficult language either for the most part, just with an addiction on pointless verbosity. C# is ... strange though. TIOBE has it ranked #5 right below Java, so there must be many C# users, but I don't get to see them really in the Linux ecosystem. So where are these people all? Using Windows only? When the question is "most developers don't use feature xyz", do all of them actually KNOW these features? You can still find many java tutorial where people use archaic ways to, for instance, iterate over a collection. Perhaps it is similar to the C# ecosystem, people are slow to adopt. Or, and this may also be a reason, people could have moved to other languages. This may not be a huge percentage, but you see that some languages suddenly struggle with old devs and failing to get new devs (ruby is in this problem right now; it may overcome it but right now it is sinking hard, even though I would reason that the language is, for the most part, better than it was in, say, 2010).
C# has had the reputation of not being viable for Linux for a long time. Therefore, the people already on Linux didn't have a reason to use it or even try it. If you're already doing stuff in other languages it's hardly worth it to switch to C#.
I personally use it quite a lot - but I came as a windows user writing all my utilities in C#. Also, afaik C# is mostly used in corporate environments that don't open-source their projects. You're unlikely to hear from it unless you're working on it for this very reason.
Mono was in a usable state on Linux for literal decades before becoming official and integrated into what is core today, that is unless you needed windows forms, which much like MSFT UI frameworks today had multiple failed attempts spanning those same decades...
Only if you didn't care about performance.
We attempted a move to mono for backend web services maybe 3 years before .NET Core released, and it was a complete no-go. 10x reduction in performance on the same hardware.
This wasn't specific to our workload either. I was big into the game Terraria at the time, and I saw similarly poor performance when running it's game server under mono vs .NET 4.x
While some of the mono toolchain was integrated into .NET Core, CoreCLR was a rewrite and immediately solved this problem.
A lot of C#'s reputation for not being viable for Linux came from the other direction and a lot of FUD against Mono. There were a lot of great Linux apps that were Linux first and/or Linux only (often using Gtk# as UI framework of choice) like Banshee and Tomboy that also had brief bundling as out-of-the-box Gnome apps in a couple of Linux distros before anti-Mono backlash got them removed.
Also, yeah today Linux support is officially maintained in modern .NET and many corporate environments are quietly using Linux servers and Linux docker containers every day to run their (closed source) projects. Linux support is one of the things that has saved companies money in running .NET, so there's a lot of weird quiet loyalty to it just from a cost cutting standpoint. But you don't hear a lot about that given the closed-source/proprietary nature of those projects. That's why it is sometimes referred to as "dark matter development" from "dark matter developers", a lot of it is out there, a lot of it doesn't get noticed in HN comments and places like that, it's all just quietly chugging along and doesn't seem to impact the overall reputation of the platform.
C# has a unique powerful position in the video game space. In almost every other niche, there are better (or just trendier) solutions, but C# is the only major language that actually gives you a combination of features that are important in video games:
- Dynamic runtime with loose coupling and hot reload of code - extremely useful during development.
- Value types. You don't want every Vector4 to be heap allocated when you're doing 3D graphics, because that's going to be absolutely unusable.
- Access to a relatively low-level substrate for basically-native performance when needed - extremely useful when you're trying to actually ship something that runs well.
Taken in isolation, C# isn't best in class for any of them, but no other language offers all three, especially not if you also want things like a really good debugger and great IDE tools.
To my knowledge, Java has none of these features (yet), and they aren't really important in a lot of the areas where Java is popular. But this is why C# in particular is very strong in the video games niche.
I think these are all valid arguments, but I do want to point out that Java is addressing them.
The first bullet is possible with the JetBrainsRuntime, a fork of OpenJDK: https://github.com/JetBrains/JetBrainsRuntime
The second bullet is a headline goal of Project Valhalla, however it is unlikely to be delivered in quite the way that a C# (or Go or Rust etc.) developer might expect. The ideal version would allow any object with purely value semantics [1] to be eligible for heap flattening [2] and/or scalarization [3], but in experimental builds that are currently available, the objects must be from a class marked with the "value" qualifier; importantly, this is considered an optimization and not a guarantee. More details: https://openjdk.org/projects/valhalla/value-objects
The third bullet (IIUC) is addressed with the Foreign Function & Memory API, though I'll admit what I've played around with so far is not nearly as slick as P/Invoke. See e.g. https://openjdk.org/jeps/454
[1] value semantics means: the object is never on either side of an == or != comparison; the equals and hashCode methods are never called, or are overridden and their implementation doesn't rely on object identity; no methods are marked synchronized and the object is never the target of a synchronized block; the wait, notify, and notifyAll methods are never called; the finalize method is not overridden and no cleaner is registered for the object; no phantom or weak references are taken of the object; and probably some other things I can't think of
[2] heap flattening means that an object's representation when stored in another object's field or in an array is reduced to just the object's own fields, removing the overhead from storing references to its class and monitor lock
[3] scalarization means that an object's fields would be stored directly on the stack and passed directly through registers
The third bullet is also presumably referring to C#'s ancient wider support for unsafe { } blocks for low level pointer math as well as the modern tools Span<T> and Memory<T> which are GC-safe low level memory management/access/pointer math tools in modern .NET. Span<T>/Memory<T> is a bit like a modest partial implementation of Rust's borrowing mechanics without changing a lot of how .NET's stack and heap work or compromising as much on .NET's bounds checking guarantees through an interesting dance of C# compiler smarts and .NET JIT smarts.
The FFM API actually does cover a lot of the same ground, albeit with far worse ergonomics IMO. To wit,
- There is no unsafe block, instead certain operations are "restricted", which currently causes them to emit warnings that can be suppressed on a per-module basis; it seems the warnings will turn into exceptions in the future
- There is no "fixed" statement and frankly nothing like it all, native code is just not allowed to access managed memory period; instead, you set up an arena to be shared between managed and native code
- MemorySegment is kinda like Memory<T>/Span<T> but harder to actually use because Java's type-erased generics are useless here
- Setting up a MemoryLayout to describe a struct is just not as nice as slapping layout attributes on an actual struct
- Working with VarHandle is just way more verbose than working with pointers
> - There is no unsafe block, instead certain operations are "restricted", which currently causes them to emit warnings that can be suppressed on a per-module basis; it seems the warnings will turn into exceptions in the future
Which sounds funny because C# effectively has gone the other direction. .NET's Code Access Security (CAS) used to heavily penalize unsafe blocks (and unchecked blocks, another relative that C# has that I don't think has a direct Java equivalent), limiting how libraries could use such blocks without extra mandatory code signing and permissions, throwing all sorts of weird runtime exceptions in CAS environments with slightly wrong permissions. CAS is mostly gone today so most C# developers only ever really experience compiler warnings and warnings-as-errors when trying to use unsafe (and/or unchecked) blocks. More libraries can use it for low level things than used to. (But also fewer libraries need to now than used to, thanks to Memory<T>/Span<T>.)
> There is no "fixed" statement and frankly nothing like it all, native code is just not allowed to access managed memory period; instead, you set up an arena to be shared between managed and native code
Yeah, this seems to be an area that .NET has a lot of strengths in. Not just the fixed keyword, but also a direct API for GC pinning/unpinning/locking and many sorts of "Unsafe Marshalling" tools to provide direct access to pointers into managed memory for native code. (Named "Unsafe" in this case because they warrant careful consideration before using them, not because they rely on unsafe blocks of code.)
> MemorySegment is kinda like Memory<T>/Span<T> but harder to actually use because Java's type-erased generics are useless here
It's the ease of use that really makes Memory<T>/Span<T> shine. It's a lot more generally useful throughout the .NET ecosystem (beyond just "foreign function interfaces") to the point where a large swathe of the BCL (Base Class Library; standard library) uses Span<T> in one fashion or another for easy performance improvements (especially with the C# compiler quietly preferring Span<T>/ReadOnlySpan<T> overloads of functions over almost any other data type, when available). Span<T> has been a "quiet performance revolution" under the hood of a lot of core libraries in .NET, especially just about anything involving string searching, parsing, or manipulation. Almost none of those gains have anything to do with calling into native code and many of those performance gains have also been achieved by eliminating native code (and the overhead of transitions to/from it) by moving performance-optimized algorithms that were easier to do unsafely in native code into "safe" C#.
It's really cool what has been going on with Span<T>. It's really wild some of the micro-benchmarks of before/after Span<T> migrations.
Related to the overall topic, it's said Span<T> is one of the reasons Unity wants to push faster to modern .NET, but Unity still has a ways to go to where it uses enough of the .NET coreclr memory model to take real advantage of it.
There is a huge amount of closed source, ecommerce shops that use C#. Its popular in game dev too but TIOBE is probably attributable to the ecommerce.
If you know Java, you should give C# a try. Its a slicker Java with some good decisions that actually make it viable for a lot of things Java struggles at, like better interop with pointers and things like native UI (Maybe Loom will eventually overtake async/await for UI dev but not quite yet).
It works very well on many more use cases than it used to. .NET has followed with the Linux servers in the cloud industry reality and is very stable.
Its just a good language. Try it out.
We are deploying .NET (Core) on Linux since soon 10 years. With hundreds of devs and dozens of services on thousands of servers.
We have not touched a Windows server in years.
In 2026 .NET is deployed like anything else into Linux containers and Lambdas. Obviously there are still people who love their ui based servers but that is because of that and not because of .NET.
Adoption of new features is gradually and explorative, obviously in an enterprise everything is on a LTS 2-3 back but adoption after that delay is rather quick to my observation.
.NET like Java have this "it is there and we extend it. And the new platform is also .NET because you have your dozens of devs already"
> I don't get to see them really in the Linux ecosystem
Sounds like you're just in a part of the ecosystem that doesn't touch C#. Aside from gamedev, C# is mostly a back-end web language, and almost all new builds are going to deploy to some Linux based serverless offering or something like Kubernetes. At my work, we're a mix of Python/Typescript/C# deploying to Google Kubernetes Engine and Cloud Run.
As for the language, your parallels to Java are accurate but outdated. Older versions of C# were very very similar to Java, but C# progressed when Java didn't. For better or worse (depending on your tastes), C# is more comparable to Kotlin or Swift these days than Java.
That's to say nothing of C#'s contributions to other programming languages; async/await and unsafe{} blocks were both first seen in C# before being adopted elsewhere.
Doesn't hn clean article titles? This is a classic click baity one. Most of these are just basic features. Some are unused for good reason.
- Property: the inspector doesn't call your getter or setter. I do use them still because i like to centralize my validation logic. But need custom machinery to make them behave consistently.
- Tuple: well-known. Good but only in moderation.
- Linq: people avoid it due to allocations, not runtime. While it is possible to avoid dynamic alloc, it is not obvious and best avoided. Also the point about the linq syntax being "cleaner" is debatable.
- Record: good. Lesser known as it's the newest in the article. No footguns like the other.
While it is nice that this is human written, the seo format is nearly as annoying as those ai articles.