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.
> Just don't write bad code;
If we're writing good code then why do we even need a GC? Heh.
In decades of experience I've never once worked in an organisation where "don't write bad code" applied. I have seen people with decades of experience with C# who don't know that IQuerable and IEnumerable load things into memory differently. I don't necessarily disagree with you that people should "just write good code", but the fact is that most of us don't do that all the time. I guess you could also argue that principles like "foureyes" would help, but it doesn't, even when they are enforced by leglisation with actual risk of punishments like DORA or NIS2.
This is the reason I favour Go as a cross platform GC language over C#, because with Go are given fewer opportunities to fuck up. There is still plenty of chance to do it, but fewer than other GC languages. At least on the plusside for .NET 10 they're going to improve IEnumerable with their devirtualization.
> hidden indirection and runtime magic"
Maybe not in C#, but C# is .NET and I don't think it's entirely fair to decouple C# from .NET and it's many frameworks. Then again, I could have made it more clear.
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.
Reminds me with C advices: "Just don't write memory leaks & UAF!".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.
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).
A common way to work around this is to provide a `IsSet` boolean:
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.
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.
> 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?
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.