Not Hacker News Logo

Not

Hacker

News!

Home
Hiring
Products
Companies
Discussion
Q&A
Users
Not Hacker News Logo

Not

Hacker

News!

AI-observed conversations & context

Daily AI-observed summaries, trends, and audience signals pulled from Hacker News so you can see the conversation before it hits your feed.

LiveBeta

Explore

  • Home
  • Hiring
  • Products
  • Companies
  • Discussion
  • Q&A

Resources

  • Visit Hacker News
  • HN API
  • Modal cronjobs
  • Meta Llama

Briefings

Inbox recaps on the loudest debates & under-the-radar launches.

Connect

© 2025 Not Hacker News! — independent Hacker News companion.

Not affiliated with Hacker News or Y Combinator. We simply enrich the public API with analytics.

Not Hacker News Logo

Not

Hacker

News!

Home
Hiring
Products
Companies
Discussion
Q&A
Users
  1. Home
  2. /Discussion
  3. /What .NET 10 GC changes mean for developers
  1. Home
  2. /Discussion
  3. /What .NET 10 GC changes mean for developers
Last activity about 1 month agoPosted Oct 1, 2025 at 4:40 AM EDT

What .net 10 Gc Changes Mean for Developers

roxeem
292 points
247 comments

Mood

calm

Sentiment

mixed

Category

other

Key topics

.net
Garbage Collection
Performance Optimization
Debate intensity60/100
See also: Preparing for the .NET 10 GC - https://news.ycombinator.com/item?id=45358527

The article discusses the changes to the .NET 10 Garbage Collector (GC) and their implications for developers, sparking a discussion on the benefits and potential drawbacks of these changes.

Snapshot generated from the HN discussion

Discussion Activity

Very active discussion

First comment

4d

Peak period

155

Week 1

Avg / period

53.3

Comment distribution160 data points
Loading chart...

Based on 160 loaded comments

Key moments

  1. 01Story posted

    Oct 1, 2025 at 4:40 AM EDT

    about 2 months ago

    Step 01
  2. 02First comment

    Oct 5, 2025 at 1:15 AM EDT

    4d after posting

    Step 02
  3. 03Peak activity

    155 comments in Week 1

    Hottest window of the conversation

    Step 03
  4. 04Latest activity

    Oct 18, 2025 at 10:54 AM EDT

    about 1 month ago

    Step 04

Generating AI Summary...

Analyzing up to 500 comments to identify key contributors and discussion patterns

Discussion (247 comments)
Showing 160 comments of 247
mrcsharp
about 2 months ago
1 reply
On the topic of DATAS, there was a discussion here recently: https://news.ycombinator.com/item?id=45358527
dang
about 2 months ago
Thanks! Macroexpanded:

Preparing for the .NET 10 GC - https://news.ycombinator.com/item?id=45358527 - Sept 2025 (60 comments)

dtech
about 2 months ago
4 replies
Interesting, I mostly work in JVM, and am always impressed how much more advanced feature-wise the .NET runtime is.

Won't this potentially cause stack overflows in programs that ran fine in older versions though?

sbinder
about 2 months ago
2 replies
I don't think the runtime is "much more advanced", the JVM has had most of these optimizations for years.
grumpyprole
about 2 months ago
2 replies
The JVM famously boxes everything though, probably because it was originally designed to run a dynamic language. An array list of floats is an array list of pointers. This created an entire cottage industry of alternative collections libraries with concrete array list implementations.
ysleepy
about 2 months ago
1 reply
A float[] is packed and not a list of pointers in the jvm.

An ArrayList<Float> is a list of pointers though.

grumpyprole
about 2 months ago
1 reply
Arrays have a static fixed size though, making them far less useful in practice. Anything one builds with generics is boxed. Dotnet doesn't have this problem.
pjmlp
about 2 months ago
1 reply
Currently you can get around this with Panama, even if the API is kind of verbose for the purpose.

Eventually value classes might close the gap, finally available as EA.

mrsmrtss
about 2 months ago
1 reply
Valhalla is over 10 years in the works already and there is still no clear date when or if at all it would be released. It's very difficult to change (or fix) such fundamental things so late in the game.
pjmlp
about 2 months ago
Because it is a huge engineering effort to add value types without breaking existing binary libraries.

Doing a Python 3 would mean no one wanted going to adopt it.

Yes it is long process.

Some of the JEP in the last versions are the initial baby steps for integration.

vips7L
about 2 months ago
They're famously working on changing that. I think we're all hopeful that we'll start seeing the changes from Valhalla roll in post-25.
dtech
about 2 months ago
1 reply
Almost none of this is in the JVM. Escape analysis is extremely limited on the standard JVM, and it's one of GraalVM's "enterprise" features. You have to pay for it.
doikor
about 2 months ago
> one of GraalVM's "enterprise" features. You have to pay for it.

Free for some (most?) use cases these days.

Basically enterprise edition does not exist anymore as it became the "Oracle GraalVM" with a new license.

https://www.graalvm.org/faq/

kimixa
about 2 months ago
1 reply
One limitation of the stack is that it needs to be contiguous virtual addresses, so it was often limited when devices just didn't have the virtual address space to "waste" on a large stack for every thread in a process.

But 64 bits of virtual address space is large enough that you can keep the stacks far enough apart that even for pretty extreme numbers of threads you'll run out of physical memory before they start clashing. So you can always just allocate more physical pages to the stack as needed, similar to the heap.

I don't know if the .net runtime actually does this, though.

throw-qqqqq
about 2 months ago
1 reply
> So you can always just allocate more physical pages to the stack as needed, similar to the heap.

You set the (max) stack size once when you create the thread and you can’t increase the (max) size after that.

Processes see a virtual address space that is handled by the OS, so you would have to involve the OS if you needed to add to the stack size dynamically.

kimixa
about 2 months ago
After process start and it's initial state, the stack is just another virtual address and the just sp another register. It can be mapped, remapped, changed to point to whatever. With overcommit even the initial state may not be entirely backed by physical pages.

Many userspace apps already do custom stack handling, it's how things like green threads work. And many non-native runtimes like .net already have custom handling for their managed stacks, as they often have different requirements and limitations to the "native" stack, and often incompatible formats and trying to isolate from possible bugs means there's less benefit to sharing the same stack with "native" code.

samus
about 2 months ago
I am surprised that they didn't already do a lot of optimizations informed by escape analysis, even though they have had value types from the beginning. Hotspot is currently hampered by only having primitive and reference types, which Project Valhalla is going to rectify.
adzm
about 2 months ago
> Won't this potentially cause stack overflows in programs that ran fine in older versions though?

That's certainly a possibility, and one that's come up before even between .net framework things migrated to .net core. Though usually it's a sign that something is awry in the first place. Thankfully the default stack sizes can be overridden with config or environment variables.

highwaylights
about 2 months ago
5 replies
Very mixed feelings about this as there’s a strong case for the decisions made here but it also moves .NET further away from WASMGC, which makes using it in the client a complete non-starter for whole categories of web apps.

It’s a missed opportunity and I can’t help but feel that if the .NET team had gotten more involved in the proposals early on then C# in the browser could have been much more viable.

pjmlp
about 2 months ago
2 replies
Webassembly taking off on the browser is wishful thinking.

There are a couple unicorns like Figma and that is it.

Performance is much better option with WebGPU compute, and not everyone hates JavaScript.

Whereas on the server it is basically a bunch of companies trying to replicate application servers, been there done that.

breve
about 2 months ago
1 reply
> Webassembly taking off on the browser is wishful thinking.

It has taken off in the browser. If you've ever used Google Sheets you've used WebAssembly.

pjmlp
about 2 months ago
1 reply
Another niche use case.
breve
about 2 months ago
2 replies
Google Sheets is one of the most widely used applications on the planet. It's not niche.

Amazon switched their Prime Video app from JavaScript to WebAssembly for double the performance. Is streaming video a niche use case?

easton
about 2 months ago
1 reply
I think they meant most people aren’t building a high performance spreadsheet, not most people aren’t using a high performance spreadsheet.
breve
about 2 months ago
2 replies
> most people aren’t building a high performance spreadsheet

Lots of people are building Blazor applications:

https://dotnet.microsoft.com/en-us/apps/aspnet/web-apps/blaz...

> not most people aren’t using a high performance spreadsheet

A spreadsheet making use of WebAssembly couldn't be deployed to the browser if WebAssembly hadn't taken off in browsers.

Practical realities contradict pjmlp's preconceptions.

pjmlp
about 2 months ago
1 reply
Don't mix mainstream adoption at the same level as regular JavaScrip and Typescript, with availability.

Microsoft would wish Blazor would take off like React and Angular, in reality it is seldom used outside .NET shops intranets in a way similar to WebForms.

breve
about 2 months ago
1 reply
> Blazor is seldom used outside .NET shops intranets

So, in other words, widely used in lots and lots of deployments.

pjmlp
about 2 months ago
Do you have a number for us?
koakuma-chan
about 2 months ago
Can you actually build something like Figma in Blazor? Does Blazor somehow facilitate that?
lanyard-textile
about 2 months ago
I think that was sarcasm :)
whizzter
about 2 months ago
1 reply
I wouldn't be surprised if it did take off, classic Wasm semantics were horrible since you needed a lot of language support to even have simple cludges when referring to DOM objects via indices and extra lifeness checking.

WASM-GC will remove a lot of those and make quite a few languages possible as almost first-class DOM manipulating languages (there's still be cludges as the objects are opaque but they'll be far less bad since they can at least avoid external ID mappings and dual-GC systems that'll behave leakily like old IE ref-counts did).

pjmlp
about 2 months ago
2 replies
All great and dandy, except tooling still sucks.

You still need to usually install plenty of moving pieces to produce a wasm file out of the "place language here", write boilerplate initialisation code, debugging is miserable, only for a few folks to avoid writing JavaScript.

Rohansi
about 2 months ago
1 reply
I think you may be underestimating how many people really dislike JavaScript.
pjmlp
about 2 months ago
As many that dislike PHP, C, C++, yet here we are.
whizzter
about 2 months ago
There will always be enthusiasts to take the initial steps, the question is if they have the taste to make it a coherent system that isn't horrible to use.

Counted out over N languages, we should see something decent land before long.

Rohansi
about 2 months ago
1 reply
.NET was already incompatible with WASM GC from the start [1]. The changes in .NET 10 are nothing in comparison to those. AFAIK WASM GC was designed with only JavaScript in mind so that's what everyone is stuck with.

[1] https://github.com/dotnet/runtime/issues/94420

whizzter
about 2 months ago
There's 2 things,

1: JavaScript _interoperability_ , ie same heap but incompatible objects (nobody is doing static JS)

2: Java, Schemes and many other GC derived languages ,etc have more "pure" GC models, C# traded some of it for practicality and that would've required some complications to the regular JS GC's.

Kwpolska
about 2 months ago
Those changes affect the .NET runtime, designed for real computers. This does not preclude the existence of a special runtime designed for Wasm with WasmGC support.

The .NET team appears to be aware of WasmGC [0], and they have provided their remarks when WasmGC was being designed [1].

[0] https://github.com/dotnet/runtime/issues/94420

[1] https://github.com/WebAssembly/gc/issues/77

torginus
about 2 months ago
A lot of the features here, stuff like escape analysis for methods etc. does not directly involve the GC - it reduces the amount of objects that go to the GC heap so the GC has less work to do in the first place.
mdasen
about 2 months ago
How would this move .NET further away from WASMGC? This is a new GC for .NET, but doesn't add new things to the language that would make it harder to use WASMGC (nor easier).

For example, .NET has internal pointers which WASMGC's MVP can't handle. This doesn't change that so it's still a barrier to using WASMGC. At the same time, it isn't adding new language requirements that WASMGC doesn't handle - the changes are to the default GC system in .NET.

I agree it's disappointing that the .NET team wasn't able to get WASMGC's MVP to support what .NET needs. However, this change doesn't move .NET further away from WASMGC.

cake-rusk
about 2 months ago
4 replies
Are you now allowed to benchmark the .Net runtime / GC?

Edit: Looks like you are allowed to benchmark the runtime now. I was able to locate an ancient EULA which forbade this (see section 3.4): https://download.microsoft.com/documents/useterms/visual%20s...

> You may not disclose the results of any benchmark test of the .NET Framework component of the Software to any third party without Microsoft’s prior written approval.

Krutonium
about 2 months ago
1 reply
...Were you not before?
cake-rusk
about 2 months ago
4 replies
IIRC the EULA forbids it. This is why you don't see .net v/s Java GC comparisons for example.
rezonant
about 2 months ago
1 reply
I can't find mention of anything resembling this. The .NET runtime is under the MIT license.
jfyi
about 2 months ago
1 reply
https://download.microsoft.com/documents/useterms/visual%20s...

It's because you aren't looking at 20 year old EULA's

>3.4 Benchmark Testing. The Software may contain the Microsoft .NET Framework. You may not disclose the results of any benchmark test of the .NET Framework component of the Software to any third party without Microsoft’s prior written approval.

This person is not likely familiar with the history of the .net framework and .net core because they decided a long time ago they were never going to use it.

cake-rusk
about 2 months ago
3 replies
Yeah, you got me there. I have moved on to Linux development since then. Haven't kept up with Microsoft developer tools.
adzm
about 2 months ago
2 replies
.net core on Linux works great btw.
homebrewer
about 2 months ago
2 replies
As long as it's your deployment target and nothing else. For development, both macOS and Linux continue to be second class citizens, and I don't see this changing as it goes against their interests. In most .NET shops around me, the development and deployment tooling is so closely tied to VS that you can't really not use it.

It's fine if you stick to JetBrains and pay for their IDE (or do non-commercial projects only), and either work in a shop which isn't closely tied to VS (basically non-existent in my area), or work by yourself.

SideburnsOfDoom
about 2 months ago
1 reply
Well, in most .NET shops around me:

> The development and deployment tooling is so closely tied to VS that you can't really not use it.

Development tooling: It's 50-50. Some use Visual Studio, some use Rider. It's fine. The only drawback is that VS Live Share and the Jetbrains equivalent don't interoperate.

deployment tooling: There is deployment tooling tied to the IDE? No-one uses that, it seems like a poor idea. I see automated build/test/deploy pipelines in GitHib Actions, and in Octopus Deploy. TeamCity still gets used, I guess.

It's true though that the most common development OS is Windows by far (with Mac as second) and the most common deployment target by far is Linux.

However the fact that there is close to no friction in this dev vs deploy changeover means that the cross-platform stuff just works. At least for server-side things such as HTTP request and queue message processing. I know that the GUI toolkit story is more complex and difficult, but I don't have to deal with it at all so I don't have details or recommendations.

aksss
about 2 months ago
> is there deployment tooling tied to the IDE?

VS has the “Publish” functionality for direct deployment to targets. It works well for doing that and nothing else. As you said, CI/CD keeps deployment IDE agnostic and has far more capabilities (e.g. Azure DevOps, GitHub Actions).

aiisthefiture
about 2 months ago
No. My entire office is Linux and macOS. Not a single windows machine. Mixture of people using VS Code and Rider. No issues building and deploying to Linux. We pay for rider. Pay nothing for vscode.
SideburnsOfDoom
about 2 months ago
In recent versions (i.e. since .NET 5 in 2020) ".NET core" is just called ".NET"

The cross-platform version is mainstream, and this isn't new any more.

.NET on Linux works fine for services. Our .NET services are deployed to Linux hosts, and it's completely unremarkable.

jfyi
about 2 months ago
1 reply
Yeah? Ncurses still a thing? I only ask because that's the only api name I remember from forever ago.

I worked on a mud on linux right after high school for awhile. Spent most of the time on the school's bsdi server prior to that though.

Then I went java, and as they got less permissive and .net got more permissive I switched at some point. I've really loved the direction C# has gone merging in functional programming idioms and have stuck with it for most personal projects but I am currently learning gdscript for some reason even though godot has C# as an option.

homebrewer
about 2 months ago
2 replies
The only thing that has become "less permissive" is Oracle's proprietary OpenJDK build, which isn't really needed or recommended in 99.9% of cases (except for when the vendor of your proprietary application requires it to provide support).

The rest of the ecosystem is "more permissive" than .NET since there are far more FOSS libraries for every task under the sun (which don't routinely go commercial without warnings), and fully open / really cross-platform development tooling, including proper IDEs.

jfyi
about 2 months ago
Funny, because one the libraries I was using at the time went hyper commercial (javafxports). Java burned me on two fronts at the very same time and lost me. Ymmv I guess. It's always a good time to try something new anyway... I also moved to kotlin on android and couldn't be happier with it, it's a clearly superior language.
mrsmrtss
about 2 months ago
The fact that you even need to be very careful when choosing a JDK is a lot bigger problem than some simple easily replaceable library is going commercial (not that this has not happend also in Java land). Also .NET is fully open and really cross-platform for a long time already and it includes more batteries than Java out of the box, you may not even need to include any third party dependencies (although there are also plenty to choose - 440k packages in Nuget). .NET has also proper IDEs or is Jetbrains Rider not a proper IDE for you?
369548684892826
about 2 months ago
As a dotnet developer all my code these days is run on Linux.
Traubenfuchs
about 2 months ago
What are you talking about?

https://benchmarksgame-team.pages.debian.net/benchmarksgame/...

magicalhippo
about 2 months ago
I seem to vaguely recall such a thing from way back in the early days, but the only copy[1] of the .Net Framework EULA I could readily find says it's OK as long as you publish all the details.

[1]: https://docs.oracle.com/en/industries/food-beverage/micros-w...

zenmac
about 2 months ago
Wow didn't know that. Can you provide some links?
deely3
about 2 months ago
Yes, you probably mixed it with SQL Server.

> Publishing SQL Server benchmarks without prior written approval from Microsoft is generally prohibited by the standard licensing agreements.

msk-lywenn
about 2 months ago
why wouldn't you be?
aiisthefiture
about 2 months ago
Yes.
elmigranto
about 2 months ago
5 replies
Use managed language, it will handle memory stuff for you, you don’t have to care.

But also read these 400 articles to understand our GC. If you are lucky, we will let you change 3 settings.

bob1029
about 2 months ago
1 reply
You can provide your own GC implementation if you really wanted to:

https://learn.microsoft.com/en-us/dotnet/core/runtime-config...

https://github.com/dotnet/runtime/blob/main/src/coreclr/gc/g...

JaggerJo
about 2 months ago
Interesting!
samus
about 2 months ago
You need these settings when you drive your application hard into circumstances where manual memory allocation arguably starts making sense again. Like humongous heaps, lots of big, unwieldy objects, or tight latency (or tail latency) requirements. But unless you're using things like Rust or Swift, the price of memory management is the need to investigate segmentation faults. I'd prefer to spend developer time on feature development and benchmarking instead.
pjmlp
about 2 months ago
Dr. Dobbs and The C/C++ Users Journal archives are full of articles and ads for special memory allocators, because the ones on the standard library for C or C++ also don't work in many cases, they are only good enough as general purpose allocation.
phito
about 2 months ago
It works just fine out of the box. The articles/manuals are just if you want to really understand how it works and get the most out of it. What's the issue with that?
CharlieDigital
about 2 months ago
In my 20+ years using C#, there's only been one instance where I needed to explicitly control some behavior of the GC (it would prematurely collect the managed handle on a ZMQ client) and that only required one line of code to pin the handle.

It pretty much never gets in your way for probably 98% of developers.

1a527dd5
about 2 months ago
1 reply
DATAS has been great for us. Literally no effort, upgrade the app to net8 and flip it on. Huge reduction in memory.

TieredCompilation on the other hand caused a bunch of esoteric errors.

neonsunset
about 2 months ago
FWIW Tiered Compilation has been enabled on by default since .NET Core 3.1. If the code tries to use refection to mutate static readonly fields and fails, it's the fault of that code.
adzm
about 2 months ago
2 replies
A hobby audio and text analysis application I've written, with no specific concern for low level performance other than algorithmically, runs 4x as fast in .net10 vs .net8. Pretty much every optimization discussed here applies to that app. Great work, kudos to the dotnet team. C# is, imo, the best cross platform GC language. I really can't think of anything that comes close in terms of performance, features, ecosystem, developer experience.
grumpyprole
about 2 months ago
4 replies
> C# is, imo, the best cross platform GC language. I really can't think of anything that comes close

How about F#? Isn't F# mostly C# with better ergonomics?

Xss3
about 2 months ago
4 replies
Lmao, functional programming is far from ergonomic
grumpyprole
about 2 months ago
2 replies
F# is hardly modern functional programming. It's more like a better python with types. And that's much more ergonomic than C#.
ZenoArrow
about 2 months ago
1 reply
Python and F# are not very similar. A better comparison is OCaml. F# and OCaml are similar. They're both ML-style functional languages.
grumpyprole
about 2 months ago
1 reply
I'd much rather code F# than Python, it's more principled, at least at the small scale. But F# is in many ways closer to modern mainstream languages than a modern pure functional language. There's nothing scary about it. You can write F# mostly like Python if you want, i.e. pervasive mutation and side effects, if that's your thing.
ZenoArrow
about 2 months ago
1 reply
If Python is the only language you have to compare other languages to, all other programming languages are going to look like "Python with X and Y differences". It makes no sense to compare Python to F# when OCaml exists and is a far closer relative. F# isn't quite "OCaml on .NET" but it's pretty close.
grumpyprole
about 2 months ago
1 reply
It absolutely does make sense to compare it to the worlds most popular programming language, especially when dismissed as "functional programming". Who benefits from an OCaml comparison? You think F# should be marketed to OCaml users who might want to try dotnet? That's a pretty small market.
ZenoArrow
about 2 months ago
1 reply
Python is the world's most used scripting language, but for application programming languages there are other languages that are widely used and better to compare to F#. For example, C# and Java.
grumpyprole
about 2 months ago
F# was pitched by Microsoft to be used in areas where Python dominates, especially for scripting in the finance domain and "rapid application development". So it doesn't make sense at all that C# and Java are a "better comparison".
raincole
about 2 months ago
2 replies
It's so weird to describe F# as "Python with Types." First of all, Python is Python with Types. And C# is much more similar to Python than F# is.
grumpyprole
about 2 months ago
2 replies
It all depends on the lens one chooses to view them. None of them are really "functional programming" in the truly modern sense, even F#. As more and more mainstream languages get pattern matching and algebraic data types (such as Python), feature lambdas and immutable values, then these languages converge. However, you don't really get the promises of functional programming such as guaranteed correct composition and easier reasoning/analysis, for that one needs at least purity and perhaps even totality. That carries the burden of proof, which means things get harder and perhaps too hard for some (e.g. the parent poster).
maleldil
about 2 months ago
1 reply
If purity is a requirement for "real" functional programming, then OCaml or Clojure aren't functional. Regarding totality, even Haskell has partial functions and exceptions.
grumpyprole
about 2 months ago
1 reply
Both OCaml and Clojure are principled and well designed languages, but they are mostly evolutions of Lisp and ML from the 70s. That's not where functional programming is today. Both encourage a functional style, which is good. And maybe that's your definition of a "functional language". But I think that definition will get increasingly less useful over time.
maleldil
about 2 months ago
2 replies
What is an example of a real functional language for you?
grumpyprole
about 2 months ago
Haskell. But there are other examples of "pure functional programming". And the state of the art is dependently typed languages, which are essentially theorem provers but can be used to extract working code.
CharlieDigital
about 2 months ago
I, too, am curious and keep checking back for a reply!
raddan
about 1 month ago
I am an avid functional programmer (it’s my default when working on solo projects) and I teach a PL class that covers the lambda calculus, LISP, etc. But the bulk of the course uses F#. I’ve been using F# regularly for the last 13 years.

It’s pretty easy to stick to pure F# if what you want is the pure functional programming experience. But what I like about it is its pragmatism, and this is a big reason why it’s the language I chose for the course. It is by-value, eagerly evaluated by default, and has an easy-to-learn idiomatic syntax. It has a large and well-behaved standard library, and you can use C#’s excellent standard library if you need additional things (e.g., mutable data structures). I have used F# in many performance-sensitive applications, and the fact that I can say “you know, inside this function, I’m going to use mutability, raw pointers, and iteration” has been a lifesaver in some places. But because it is a functional language, I can also abstract all that away and pretend that it is functional.

I understand why other FP folks dislike this approach. But the insistence on purity makes many problems artificially difficult. Debugging a lazily evaluated program is a nightmare. There are lots of times I want a well-behaved language but I am not willing to do CS research just to solve common algorithmic problems. The generally pragmatic streak from the SML family makes them easy to love.

azkalam
about 2 months ago
Sure, Python has types as part of the syntax, but Python doesn't have types like Java, C#, etc. have types. They are not pervasive and the semantics are not locked down.
CharlieDigital
about 2 months ago
1 reply
Exactly what I've observed in practice because most devs have no background in writing functional code and will complain when asked to do so.

Passing or returning a function seems a foreign concept to many devs. They know how to use lambda expressions, but rarely write code that works this way.

We adopted ErrorOr[0] and have a rule that core code must return ErrorOr<T>. Devs have struggled with this and continue to misunderstand how to use the result type.

[0] https://github.com/amantinband/error-or

raddan
about 1 month ago
Cool. I’ll have to check this out. I often find myself rolling my own “option type” when writing C#.

Agreed with getting developers to see the value. The most convincing argument I’ve been able to make thus far has been “isn’t it embarrassing when your code explodes in production? Imagine being able to find those errors at compile time.” The few who actually understand the distinction between “compile time” and “run time” can usually appreciate why you might want it.

physPop
about 2 months ago
1 reply
honestly this sounds like you've never really done it. FP is much better for ergonomics, developer productivity, correctness. All the important things when writing code.
Hendrikto
about 2 months ago
1 reply
I like FP, but your claim is just as baseless as the parent’s.

If FP was really better at “all the important things”, why is there such a wide range of opinions, good but also bad? Why is it still a niche paradigm?

raddan
about 1 month ago
It’s niche because the vast, vast majority of programmers just continue to do what they know or go with the crowd. I spend roughly 50% of my time doing FP and 50% doing imperative (most OOP) programming. I am dramatically more effective writing functional code.

Like other posters, I am not going to claim that it is better at all things. OOP’s approach to polymorphism and extensibility is brilliant. But I also know that nearly all of the mistakes I make have to do with not thinking carefully enough about mutability or side-effects, features that are (mostly) verboten in FP. It takes some effort to re-learn how to do things (recursion all the things!) but once you’ve done it, you realize how elegant your code can be. Many of my FP programs are also effectively proofs of their own correctness, which is not a property that many other language styles can offer.

Here’s an appropriate PG essay: https://paulgraham.com/avg.html

samus
about 2 months ago
That really depends on your preferred coding style.
raincole
about 2 months ago
1 reply
Personally I love F#, but I feel the community is probably even smaller than OCaml...
gixco
about 2 months ago
I once got a temporary F# role without any F# experience simply by having 7 YoE with C# and the knowledge that F# exists.

As much as I'd like to do more with it, the "just use F#" idea flaunted in this thread is a distant pipe dream for the vast majority of teams.

denismenace
about 2 months ago
He means the runtime ".NET CLR". They have the same runtime.
actionfromafar
about 2 months ago
It is but in practice it’s very hard to find programmers for it.
devjab
about 2 months ago
4 replies
Having worked with C# professionally for a decade, going through the changes with LINQ, async/await, Roslyn, and the rise of .NET Core, to .NET Core becoming .NET, I disagree. I certainly think that C# is a great tool and that it’s the best it has ever been. It’s also relies on very implicit behaviour, it is build upon OOP design principles and a bunch of “needless” abstraction. Things I personally have come to view as anti-patterns over the years. This isn’t because I specifically dislike C#, you could find me saying something similar about Java.

I suspect that the hidden indirection and runtime magic, may be part of why you love the language. In my experience, however, it leads to poor observability, opaque control flow, and difficult debugging sessions in every organisation and company I’ve ever worked for. It’s fair to argue that this is because the people working with C# are bad at software engineering. Similar to how Uncle Bob will always be correct when he calls teams out for getting his principles wrong. To me that means the language itself has a poor design fit for software development in 2025. Which is probably why we see more and more Go adoption, due to its explicit philosophies. Though to be fair, Python seems to be “winning” as far as adoption goes in the cross platform GC language space. Having worked with Django-Ninja I can certainly see why. It’s so productive, and with stuff like Pyrefly, UV and Ruff it’s very easy to make it a YAGNI experience with decent type control.

I am happy you enjoy C# though, and it’s great to see that it is evolving. If they did more to enhance the developer experience so that people were less inclined to do bad engineering on a thursday afternoon after a long day of useless meetings. Then I would probably agree with you. I'm not sure any of the changes going toward .NET 10 are going in that direction though.

OtomotO
about 2 months ago
3 replies
I am paid to work in Java and C# among Go, Rust, Kotlin, Scala and I wholeheartedly agree.

I hate the implicitness of Spring Boot, Quarkus etc. as much as the one in C# projects.

All these magic annotations that save you a few lines of code until they don't, because you get runtime errors due to incompatible annotations.

And then it takes digging through pages of docs or even reporting bugs on repos instead of just fixing a few explicit lines.

Explicitness and Verbosity are orthogonal concepts mostly!

npodbielski
about 2 months ago
1 reply
What are those magic annotations you are talking about? Attributes? Not much of those are left in modern .net.
debugnik
about 2 months ago
3 replies
Attributes and reflection are still used in C# for source generators, JSON serialization, ASP.NET routing, dependency injection... The amount of code that can fail at runtime because of reflection has probably increased in modern C#. (Not from C# source generators of course, but those only made interop even worse for F#-ers).
mrsmrtss
about 2 months ago
1 reply
Don't we have automated tests for catching this kind of things or is everyone only YOLOing in nowadays? Serialization, routing, etc can fail at runtime regardless of using or not using attributes or reflection.
osigurdson
about 2 months ago
3 replies
Ease of comprehension is more important than tests for preventing bugs. A highly testable DI nightmare will have more bugs than a simple system that people can understand just by looking at it.
mrsmrtss
about 2 months ago
1 reply
I haven't experienced a DI 'nightmare' myself yet, but then again, we have integration tests to cover for that.
CharlieDigital
about 2 months ago
Try Nest.js and you'll know true DI "nightmares".
thatnerdyguy
about 2 months ago
If the argument is that most developers can't understand what a DI system does, I don't know if I buy that. Or is the argument it's hard to track down dependencies? Because if that's the case the idiomatic c# has the dependencies declared right in the ctor.
LinXitoW
about 2 months ago
But the "simple" system will be full of repetition and boilerplate, meaning the same bugs are scattered around the code base, and obscured by masses of boilerplate.

Isn't a GC also a Magic? Or anything above assembly? While I also understand the reluctance to use too much magic, in my experience, it's not the magic, it's how well the magic is tested and developed.

I used to work with Play framework, a web framework built around Akka, an async bundle of libraries. Because it wasn't too popular, only the most common issues were well documented. I thought I hated magic.

Then, I started using Spring Boot, and I loved magic. Spring has so much documentation that you can also become the magician, if you need to.

jakewins
about 2 months ago
1 reply
Aye, was involved in some really messed up outages from New Relics agent libraries generating bogus byte code at runtime, absolute nightmare for the teams trying to debug it because none of the code causing the crashing existed anywhere you could easily inspect it. Replaced opaque magic from new relic with simpler OTEL, no more outages
CharlieDigital
about 2 months ago
That's likely the old emit approach. Newer source gen will actually generate source that is included in the compilation.
npodbielski
about 2 months ago
1 reply
OK lets brake this down:

- code generators, I think I saw it only in regex. Logging can be done via `LoggerDefine` too so attributes are optional. Also code generators have access to full tokenized structure of code, and that means attributes are just design choice of this particular generator you are using. And finally code generators does not produce runtime errors unless code that they generated is invalid.

- Json serialization, sure but you can use your own converters. Attributes are not necessary.

- asp.net routing, yes but those are in controllers, my impression is that minimal APIs are now the go to solution and you have `app.MapGet(path)` so no attributes; you can inject services into minimal APIs and this does not require attributes. Most of the time minimal APIs does not require attributes at all.

- dependency injection, require attributes when you inject services in controllers endpoints, which I never liked nor understood why people do that. What is the use case over injecting it through controller constructor? It is not like constructor is singleton, long living object. It is constructed during Asp.net http pipeline and discarded when no longer necessary.

So occasional usage, may still occur from time to time, in endpoints and DTOs (`[JsonIgnore]` for example) but you have other means to do the same things. It is done via attributes because it is easier and faster to develop.

Also your team should invest some time into testing in my opinion. Integration testing helps a lot with catching those runtime errrors.

debugnik
about 2 months ago
1 reply
> Json serialization, sure but you can use your own converters

And going through converters is (was?) significantly slower for some reason than the built-in serialisation.

> my impression is that minimal APIs are now the go to solution and you have `app.MapGet(path)` so no attribute

Minimal APIs use attributes to explicitly configure how parameters are mapped to the path, query, header fields, body content or for DI dependencies. These can't always be implicit, which BTW means you're stuck in F# if you ever need them, because the codegen still doesn't match what the reflection code expects.

I haven't touched .NET during work hours in ages, these are mostly my pains from hobbyist use of modern .NET from F#. Although the changes I've seen in C#'s ecosystem the last decade don't make me eager to use .NET for web backends again, they somehow kept going with the worst aspects.

I'm fed up by the increasing use of reflection in C#, not the attributes themselves, as it requires testing to ensure even the simplest plumbing will attempt to work as written (same argument we make for static types against dynamic, isn't it?), and makes interop from F# much, much harder; and by the abuse of extension methods, which were the main driver for implicit usings in C#: no one knows which ASP.NET namespaces they need to open anymore.

npodbielski
about 2 months ago
I am working on entire new hobby project written on minimal apis and I checked today before writing answer to your comment: I did not used any attributes there, beside one 'FromBody' and that one only because otherwise it tries to map model from everywhere so you could in theory pass it from Query string. Which was extremely weird.

Where did you saw all of those attributes in minimal APIs? I honestly curious because from my experience - it is very forgiving and works mostly without them.

CharlieDigital
about 2 months ago
3 replies
I disagree on this.

I am at a (YC, series C) startup that just recently made the switch from TS backend on Nest.js to C# .NET Web API[0]. It's been a progression from Express -> Nest.js -> C#.

What we find is that having attributes in both Nest.js (decorators) and C# allows one part of the team to move faster and another smaller part of the team to isolate complexity.

The indirection and abstraction are explicit decisions to reduce verbosity for 90% of the team for 90% of the use cases because otherwise, there's a lot of repetitive boilerplate.

The use of attributes, reflection, and source generation make the code more "templatized" (true both in our Nest.js codebase as well as the new C# codebase) so that 90% of the devs simply need to "follow the pattern" and 10% of the devs can focus on more complex logic backing those attributes and decorators.

Having the option to dip into source generation, for example, is really powerful in allowing the team to reduce boilerplate.

[0] We are hiring, BTW! Seeking experienced C# engineers; very, very competitive comp and all greenfield work with modern C# in a mixed Linux and macOS environment.

badhombres
about 2 months ago
4 replies
The trade offs are though that patterns and behind the scenes source code generation is another layer that the devs who have to follow need to deal with when debugging and understanding why something isn’t working. They either spend more time understanding the bespoke things or are bottle necked relying on a team or person to help them get through those moments. It’s a trade off and one that has bit me and others before
CharlieDigital
about 2 months ago
2 replies
Would you rather a team move faster and be more productive or be a purist and disallow abstractions to avoid some potential runtime tracing challenges which can be mitigated with good use of OTEL and logging? I don't know about you, but I'm going to bias towards productivity and use integration tests + observability to safeguard code.
hu3
about 2 months ago
1 reply
How much faster are we talking? Because you'd have to account for the time lost debugging annotations.
CharlieDigital
about 2 months ago
1 reply
What are you working on that you're debugging annotations everyday? I'd say you've made a big mistake if you're doing that/you didn't read the docs and don't understand how to use the attribute.

(Of course you are also free to write C# without any of the built in frameworks and write purely explicit handling and routing)

On the other hand, we write CRUD every day so anything that saves repetition with CRUD is a gain.

OtomotO
about 2 months ago
I don't debug them every day, but when I do, it takes days for a nasty bug to be worked out.

Yes, they make CRUD stuff very easy and convenient.

badhombres
about 2 months ago
1 reply
Disallow bespoke abstractions and use the industry standard ones instead. People who make abstractions inflate how productive they’re making everyone else. Your user base is much smaller than popular libs, so your docs and abstractions are not as battle tested and easy to use as much as you think.
CharlieDigital
about 2 months ago
1 reply
This is raw OpenFGA code:

    await client.Write(
        new ClientWriteRequest(
            [
                // Alice is an admin of form 123
                new()
                {
                    Object = "form:124",
                    Relation = "editor",
                    User = "user:avery",
                },
            ]
        )
    );

    var checkResponse = await client.Check(
        new ClientCheckRequest
        {
            Object = "form:124",
            Relation = "editor",
            User = "user:avery",
        }
    );

    var checkResponse2 = await client.Check(
        new ClientCheckRequest
        {
            Object = "form:125",
            Relation = "editor",
            User = "user:avery",
        }
    );
This is an abstraction we wrote on top of it:

    await Permissions
        .WithClient(client)
        .ToMutate()
        .Add<User, Form>("alice", "editor", "226")
        .Add<User, Team>("alice", "member", "motion")
        .SaveChangesAsync();

    var allAllowed = await Permissions
        .WithClient(client)
        .ToValidate()
        .Can<User, Form>("alice", "edit", "226")
        .Has<User, Team>("alice", "member", "motion")
        .ValidateAllAsync();
You would make the case that the former is better than the latter?
badhombres
about 2 months ago
1 reply
In the first example, I have to learn and understand OpenFGA, in the second example I have to learn and understand OpenFGA and your abstractions.
mrsmrtss
about 2 months ago
Well the point of using abstractions is that you don't need to know the things that it is abstracting. I think the abstraction here is self explaining what it does and you can certainly understand and use it without needing to understand all the specifics behind it.
germandiago
about 2 months ago
I am not talking about C# specifically but also and I agree.

Implicit and magic looks nice at first but sometimes it can be annoying. I remember the first time I tried Ruby On Rails and I was looking for a piece of config.

Yes, "convention over configuration". Namely, ungreppsble and magic.

This kind of stuff must be used with a lot of care.

I usually favor explicit and, for config, plain data (usually toml).

This can be extended to hidden or non-obvious allocations and other stuff (when I work with C++).

It is better to know what is going on when you need to and burying it in a couole of layers can make things unnecessarily difficult.

speed_spread
about 2 months ago
That's the deal with all metaprogramming.
belmont_sup
about 2 months ago
It has been worth the abstraction in my organization with many teams. Thinking 1000+ engineers, at minimum. It helps to abstract as necessary for new teammates that want to simply add a new endpoint yet follow all the legal, security, and data enforcement rules.

Better than no magic abstractions imo. In our large monorepo, LSP feedback can often be so slow that I can’t even rely on it to be productive. I just intuit and pattern match, and these magical abstractions do help. If I get stuck, then I’ll wade into the docs and code myself, and then ask the owning team if I need more help.

bcrosby95
about 2 months ago
1 reply
People were so afraid of macros they ended up with something even worse.

At least with macros I don't need to consider the whole of the codebase and every library when determining what is happening. Instead I can just... Go to the macro.

CharlieDigital
about 2 months ago
1 reply
C# source generators are...just macros?
skydhash
about 2 months ago
2 replies
They are not. They are generators. Macros tends to be local and explicit as the other commenters have said. They are more like templates. Generators can be fairly involved and feels like a mini language, one that is not as observable as macros.
CharlieDigital
about 2 months ago
Isn't this just a string template? https://github.com/CharlieDigital/SKPromptGenerator/blob/mai...

Maybe you're confusing `System.Reflection.Emit` and source generators? Source generators are just a source tree walker + string templates to write source files.

mexicocitinluez
about 2 months ago
> Generators can be fairly involved and feels like a mini language, one that is not as observable as macros.

I agree the syntax is awkward, but all it boils down to is concatenating code in strings and adding it as a file to your codebase.

And the syntax will 100% get cleaner (it;s already happening with stuff like ForAttributeWithMetadataName

com2kid
about 2 months ago
> so that 90% of the devs simply need to "follow the pattern" and 10% of the devs can focus on more complex logic backing those attributes and decorators.

Works well until the 10% that understand the behind the scenes leave and you are left with a bunch of developers copy and pasting magic patterns that they don't understand.

I love express because things are very explicit. This is the JSON schema being added to this route. This route is taking in JSON parameters. This is the function that handles this POST request endpoint.

I joined a team using Spring Boot and the staff engineer there couldn't tell me if each request was handled by its own thread or not, he couldn't tell me what variables were shared across requests vs what was uniquely instantiated per request. Absolute insanity, not understanding the very basics of one's own runtime.

Meanwhile in Express, the threading model is stupid simple (there isn't one) and what is shared between requests is obvious (everything declared in an outer scope).

pjmlp
about 2 months ago
1 reply
As polyglot developer, I also disagree.

If I wanted explicitness for every little detail I would keep writing in Assembly like in the Z80, 80x86, 68000 days.

Unfortunately we never got Lisp or Smalltalk mainstream, so we got to metaprogramming with what is available, and it is quite powerful when taken advantage of.

Some people avoid wizard jobs, others avoid jobs where magic is looked down upon.

I would also add that in the age of LLM and AI generated applications, discussing programming languages explicitness is kind of irrelevant.

skydhash
about 2 months ago
2 replies
Explicitness is different than verbosity. Often annotations and the like are abused to create a lot of accidental complexity just to not write a few keywords. In almost every lisp project you'll find that macros are not intended for reducing verbosity, they are there to define common patterns. You can have something like

  (define-route METHOD PATH BODY)
You can then easily expect the generated code. But in Java and others, you'll have something like

  @GET(path=PATH)
And there's a whole system hidden behind this, that you have to carefully understand as every annotation implementation is different.
germandiago
about 2 months ago
1 reply
This is the trade-off with macros and annotation/code-generation systems.

I tend to do obvious things whwn I use this kind of tools. In fact, I try to avoid macros.

Even if configurability is not important, I favor sinplification over reuse. In case I need reuse, I go for higher order functions if I can. Macro is the last bullet.

In some circumstances like Json or serialization maybe they can be slightly abused to mark fields and such. But whole code generation can take it so far and magic that it is not worth in many circumstances IMHO, thiugh every tool has its use cases, even macros and annotations.

skydhash
about 2 months ago
1 reply
IMO, macros and such should be to improve coding UX. But using it for abstractions and the like is very much not worth it. So something like JSX (or the loop system in Common Lisp) is good. But using it for DI is often a code smell for me.
germandiago
about 2 months ago
> IMO, macros and such should be to improve coding UX

Coding UX critically leans on familiarity and spread of knowledge. By definition, making a non-obvious macro not known by others makes the UI just worse for a definition of worse which means "less manageable by anyone that looks at it without previous knowledge".

That is also the reason why standard libraries always have an advantage in usability just because people know them or the language constructs themselves.

pjmlp
about 2 months ago
Only if those Lisp projects are done by newbies, Clojure is quite known for having a community that takes that approach to macros, versus everyone else on Lisp since its early days.

Using macros for DSLs has been common for decades, and is how frameworks like CLOS were initially implemented.

CharlieDigital
about 2 months ago
1 reply
You are missing the forest for the trees.

C# has increasingly become more terse (e.g. switch expressions, collection initializers, object initializers, etc) and, IMO, is a good balance between OOP and functional[0].

Functions are first class objects in C# and teams can write functional style C# if they want. But I suspect that this doesn't scale well in human terms as we've encountered a LOT of trouble trying to get TypeScript devs to adopt more functional programming techniques. We've found that many devs like to use functional code (e.g. Linq, `.filter()`, `.map()`), but dislike writing functional code because most devs are not wired this way and do not have any training in how to write functional code and understanding monads. Asking these devs to use a monad has been like asking kids to eat their carrots.

Across much of our backend TS codebase, there are very, very few cases where developers accept a function as input or return a function as output (almost all of it written by 1 dev out of a team of ~20).

    > ...it is build upon OOP design principles and a bunch of “needless” abstraction
Having been working with Nest.js for a while, it's clear to me that most of these abstractions are not "needless" but actually "necessary" to manage complexity of apps beyond a certain scale and the reasons are less technical and more about scaling teams and communicating concepts.

Anyone that looks at Nest.js will immediately see the similarities to Spring Boot or .NET Web APIs because it fills the same niche. Whether you call a `*Factory` a "factory" or something else, the core concept of what the thing does still exists whether you're writing C#, Java, Go, or JS: you need a thing that creates instances of things.

You can say "I never use a factory in Go", but if you have a function that creates other things or other functions, that's a factory...you're just not using the nomenclature. Good for you? Or maybe you misunderstand why there is standard nomenclature of common patterns in the first place and are associating these patterns with OOP when in reality, they are almost universal and are rather human language abstractions for programming patterns.

[0] https://medium.com/itnext/getting-functional-with-c-6c74bf27...

devjab
about 2 months ago
1 reply
I notice that none of the examples in your blog entry on functional C# deals with error handling. I know that is not the point of your article, but that is actually one of my key issues with C# and its reliance on implicit, because like so many other parts of C# you'd probably hand it over to an exeception handler. I'd much rather prefer you to deal with it explicitly right where it happens, and I would prefer if you were actually forced to do it for examples like yours. This is because implicit error handling is hard. I have no doubt you do it well, but it is frankly rare to meet a C# developer who has as much of an understanding on the language that you clearly have.

I think this is an excellent blog post by the way. My issues with C# (and this applies to a lot of other GC languages) is that most developers would learn a lot from your article. Because none of it is an intuitive part of the language philosophy.

I don't think you should never use OOP or abstractions. I don't think there is a golden rule for when you should use either. I do think you need to understand why you are doing it though, and C# sort of makes people go to abstractions first, not last in my experience. I don't think these changes to the GC is going to help people who write C# without understanding C#, which is frankly most C# developers around here. Because Go is opinionated and explicit it's simply an issue I have to deal with less in Go teams. It's not an issue I have to deal with less in Python teams, but then, everyone who loves Python knows it sucks.

CharlieDigital
about 2 months ago
1 reply
My team just recently made the switch from a TS backend to a C# backend for net new work. When we made this switch, we also introduced `ErrorOr`[0] which is a monadic result type.

I would not have imagined this to be controversial nor difficult, but it turns out that developers really prefer and understand exceptions. That's because for a backend CRUD API, it's really easy to just throw and catch at a global HTTP pipeline exception filter and for 95% of cases, this is OK and good enough; you're not really going to be able to handle it nor is it worth it to to handle it.

We'll stick with ErrorOr, but developers aren't using it as monad and simply unwrapping the value and the error because, as it turns out, most devs just have a preference/greater familiarity with imperative try-catch handling of errors and practically, in an HTTP backend, there's nothing wrong in most cases with just having a global exception filter do the heavy lifting unless the code path has a clear recovery path.

    > I don't think you should never use OOP or abstractions. I don't think there is a golden rule for when you should use either.
I do think there is a "silver rule": OOP when you need structural scaffolding, functional when you have "small contracts" over big ones. An interface or abstract class is a "big contract" that means to understand how to use it, you often have to understand a larger surface area. A function signature is still a contract, but a micro-contract.

Depending on what you're building, having structural scaffolding and "big contracts" makes more sense than having lots of micro-contracts (functions). Case in point: REST web APIs make a lot more sense with structural scaffolding. If you write it without structural scaffolding of OOP, it ends up with a lot of repetition and even worse opaqueness with functions wrapping other functions.

The silver rule for OOP vs FP for me: OOP for structural templating for otherwise repetitive code and "big contracts"; FP for "small contracts" and algorithmically complex code. I encourage devs on the team to write both styles depending on what they are building and the nature of complexity in their code. I think this is also why TS and C# are a sweet spot, IMO, because they straddle both OOP and have just enough FP when needed.

[0] https://github.com/amantinband/error-or

fabian2k
about 2 months ago
1 reply
You introduced a pattern that is simply different than the usual in C#. It's also not clearly better, it's different. In languages designed for result types like this the ergonomics of such a type are usually better.

All the libraries you use and all methods from the standard library use exceptions. So you have to deal with exceptions in any case.

There's also a million or so libraries that implement types like this. There is no standard, so no interoperability. And people have to learn the pecularities of the chosen library.

I like result types like this, but I'd never try to introduce them in C# (unless at some point they get more language support).

CharlieDigital
about 2 months ago
1 reply
These are developers that have never written C# before so there's no difference between whether it's language supported or not. It was in the core codebase on day 1 when they onboarded so it may as well have been native.

But what I takeaway from this is that Go's approach to error handling is "also not clearly better, it's different".

Even if C# had core language support for result types, you would be surprised how many developers would struggle with it (that is my takeaway from this experience).

fabian2k
about 2 months ago
1 reply
If you're not coming from a strongly typed functional language, it's still a pattern you're not used to. Which might be a bit of a roundabout way to say that I agree about your last part, developers without contact to that kind of language will struggle at first with a pattern like this.

I know how to use this pattern, but the C# version still feels weird and cumbersome. Usually you combine this with pattern matching and other functional features and the whole thing makes it convenient in the end. That part is missing in C#. And I think it makes a different in understanding, as you would usually build ony our experience with pattern matching to understand how to handle this case of Result|Error.

CharlieDigital
about 2 months ago
1 reply

    > Usually you combine this with pattern matching and other functional features and the whole thing makes it convenient in the end. That part is missing in C#
You mean like this?

    string foo = result.MatchFirst(
        value => value,
        firstError => firstError.Description);
Or this?

    ErrorOr<string> foo = result
        .Then(val => val * 2)
        .Then(val => $"The result is {val}");
Or this?

    ErrorOr<string> foo = await result
        .ThenDoAsync(val => Task.Delay(val))
        .ThenDo(val => Console.WriteLine($"Finsihed waiting {val} seconds."))
        .ThenDoAsync(val => Task.FromResult(val * 2))
        .ThenDo(val => $"The result is {val}");
With pattern matching like this?

    var holidays = new DateTime[] {...};
    var output = new Appointment(
        DayOfWeek.Friday, 
        new DateTime(2021, 09, 10, 22, 15, 0), 
        false
    ) switch
    {
        { SocialRate: true } => 5,
        { Day: DayOfWeek.Sunday } => 25,
        Appointment a when holidays.Contains(a.Time) => 25,
        { Day: DayOfWeek.Saturday } => 20,
        { Day: DayOfWeek.Friday, Time.Hour: > 12 } => 20,
        { Time.Hour: < 8 or >= 18 } => 15,
        _ => 10,
    };
C# pattern matching is pretty damn good[0] (seems you are not aware?).

[0] https://timdeschryver.dev/blog/pattern-matching-examples-in-...

fabian2k
about 2 months ago
None of your examples use native C# pattern matching. And without language support like e.g. discriminated unions you can't have exhaustive pattern matching in C#. So you'll have to silence the warnings about the missing default case or always add one, which is annoying.
feoren
about 2 months ago
2 replies
I have been coding in C# for 16 years and I have no idea what you mean by "hidden indirection and runtime magic". Maybe it's just invisible to me at this point, but GC is literally the only "invisible magic" I can think of that's core to the language. And I agree that college-level OOP principles are an anti-pattern; stop doing them. C# does not force you to do that at all, except very lightly in some frameworks where you extend a Controller class if you have to (annoying but avoidable). Other than that, I have not used class inheritance a single time in years, and 98% of my classes and structs are immutable. Just don't write bad code; the language doesn't force you to su all.
lock1
about 2 months ago
1 reply
Hidden indirection & runtime magic almost always refer to DI frameworks.

Reflection is what makes DI feel like "magic". Type signatures don't mean much in reflection-heavy codes. Newcomers won't know many DI framework implicit behaviors & conventions until either they shoot themself in their foot or get RTFM'd.

My pet theory is this kind of "magic" is what makes some people like Golang, which favors explicit wiring over implicit DI framework magic.

  > Just don't write bad code
Reminds me with C advices: "Just don't write memory leaks & UAF!".
feoren
about 2 months ago
"Just don't write bad code" means that you can easily avoid some of the anti-patterns that people list as weaknesses of C#. Yes, maybe you inherit code where people do those things, but how much of that is because of C#, and how much is due to it being popular? Any popular language is going to have bucketloads of bad code written in it. Alternatively: "you can write bad code in any language". I'm far more interested in languages that help you write great code than those that prevent you from writing bad code. (Note that I view static typing in the "help you write great code" category -- I am distinguishing "bad code" from "incorrect code" here.)

Yes, some programming languages have more landmines and footguns than others (looking at you, JS), and language designers should strive to avoid those as much as possible. But I actually think that C# does avoid those. That is: most of what people complain about are language features that are genuinely important and useful in a narrow scope, but are abused / applied too broadly. It would be impossible to design a language that knows whether you're using Reflection appropriately or not; the question is whether their inclusion of Reflection at all improves the language (it does). C# chose to be a general-purpose, multi-paradigmatic language, and I think they met that goal with flying colors.

> Newcomers won't know many DI framework implicit behaviors & conventions until either they shoot themself in their foot or get RTFM'd

The question is: does the DI framework reduce the overall complexity or not? Good DI frameworks are built on a very small number of (yes, "magic") conventions that are easy to learn. That being said, bad DI frameworks abound.

And can you imagine any other industry where having to read a few pages of documentation before you understood how to do engineering was looked upon with such derision? WTF is wrong with newcomers having to read a few pages of documentation!?

branko_d
about 2 months ago
2 replies
Some examples:

- Attributes can do a lot of magic that is not always obvious or well documented.

- ASP.NET pipeline.

- Source generators.

I love C#, but I have to admit we could have done with less “magic” in cases like these.

feoren
about 2 months ago
2 replies
Attributes do nothing at all on their own. It's someone else's code that does magic by reflecting on your types and looking for those attributes. That may seem like a trivial distinction, but there's a big difference between "the language is doing magic" and "some poorly documented library I'm using is doing magic". I rarely use and generally dislike attributes. I sometimes wonder if C# would be better off without them, but there are some legitimate usages like interop with unmanaged code that would be really awkward any other way. They are OK if you think of them as a weakly enforced part of the type system, and relegate their use to when a C# code object is representing something external like an API endpoint or an unmanaged call. Even this is often over-done.

Yes, the ASP.NET pipeline is a bit of a mess. My strategy is to plug in a couple adapters that allow me to otherwise avoid it. I rolled my own DI framework, for instance.

Source generators are present in all languages and terrible in all languages, so that certainly is not a criticism of C#. It would be a valid criticism if a language required you to use source generators to work efficiently (e.g. limited languages like VB6/VBA). But I haven't used source generators in C# in at least 10 years, and I honestly don't know why anyone would at this point.

Maybe it sounds like I'm dodging by saying C# is great even though the big official frameworks Microsoft pushes (not to mention many of their tutorials) are kinda bad. I'd be more amenable to that argument if it took more than an afternoon to plug in the few adapters you need to escape their bonds and just do it all your own way with the full joy of pure, clean C#. You can write bad code in any language.

That's not to say there's nothing wrong with C#. There are some features I'd still like to see added (e.g. co-/contra-variance on classes & structs), some that will never be added but I miss sometimes (e.g. higher-kinded types), and some that are wonderful but lagging behind (e.g. Expressions supporting newer language features).

CharlieDigital
about 2 months ago
1 reply

    > But I haven't used source generators in C# in at least 10 years, and I honestly don't know why anyone would at this point.
A challenge with .NET web APIs is that it's not possible to detect when interacting with a payload deserialized from JSON whether it's `null` because it was set to `null` or `null` because it was not supplied.

A common way to work around this is to provide a `IsSet` boolean:

    private bool _isNameSet;

    public string? Name { get; set { ...; isNameSet = true; } }
Now you can check if the value is set.

However, you can see how tedious this can get without a source Generator. With a source generator, we simply take nullable partial properties and generate the stub automatically.

    public partial string? Name { get; set; }
Now a single marker attribute will generate as many `Is*Set` properties as needed.

Of course, the other use case is for AOT to avoid reflection by generating the source at runtime.

feoren
about 2 months ago
That is tedious with or without a source generator, mainly because there's a much better way to do it:

    public Optional<string> Name;
With Optional being something like:

    class Optional<T> {
      public T? Value;
      public bool IsSet;
    }
I'm actually partial to using IEnumerable for this, and I'd reverse the boolean:

    class Optional<T> {
      public IEnumerable<T> ValueOrEmpty;
      public bool IsExplicitNull;
    }
With this approach (either one) you can easily define Map (or "Select", if you choose LINQ verbiage) on Optional and go delete 80% of your "if" statements that are checking that boolean.

Why mess with source generators? They're just making it slightly easier to do this in a way that is really painful.

I'd strongly recommend that if you find yourself wanting Null to represent two different ideas, then you actually just want those two different ideas represented explicitly, e.g. with an Enum. Which you can still do with a basic wrapper like this. The user didn't say "Null", they said "Unknown" or "Not Applicable" or something. Record that.

    public OneOf<string, NotApplicable> Name
A good OneOf implementation is here (I have nothing to do with this library, I just like it):

https://github.com/mcintyre321/OneOf

I wrote a JsonConverter for OneOf and just pass those over the wire.

branko_d
about 2 months ago
> But I haven't used source generators in C# in at least 10 years

Source generators didn't exist in C# 10 years ago. You probably had something else in mind?

mexicocitinluez
about 2 months ago
I don't really consider any of these magic, particularly source generators.

It's just code that generates code. Some of the syntax is awkward, but it's not magic imo.

ed_elliott_asc
about 2 months ago
What sort of issues do you get debugging?

My experience of .NET even from version 1 is that it has the best debugging experience of any modern language, from the visual studio debugger to sos.dll debugging crash dumps.

yread
about 2 months ago
I think that DATAS also has more knobs to tune it than the old GC. I plan to set the Throughput Cost Percentage (TCP) via System.GC.DTargetTCP to some low value so that is has little impact on latency.

https://learn.microsoft.com/en-us/dotnet/core/runtime-config...

87 more comments available on Hacker News

View full discussion on Hacker News
ID: 45435606Type: storyLast synced: 11/20/2025, 8:28:07 PM

Want the full context?

Jump to the original sources

Read the primary article or dive into the live Hacker News thread when you're ready.

Read ArticleView on HN
Not Hacker News Logo

Not

Hacker

News!

AI-observed conversations & context

Daily AI-observed summaries, trends, and audience signals pulled from Hacker News so you can see the conversation before it hits your feed.

LiveBeta

Explore

  • Home
  • Hiring
  • Products
  • Companies
  • Discussion
  • Q&A

Resources

  • Visit Hacker News
  • HN API
  • Modal cronjobs
  • Meta Llama

Briefings

Inbox recaps on the loudest debates & under-the-radar launches.

Connect

© 2025 Not Hacker News! — independent Hacker News companion.

Not affiliated with Hacker News or Y Combinator. We simply enrich the public API with analytics.