Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

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.


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!".


"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!?


> 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.


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).


> 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?


Huh? 10 years ago was 2015. Entity Framework had been around for nearly 7 years by then, and as far back as I remember using it, it used source generators to subclass your domain models. Even with "Code First", it generated subclasses for automatic property tracking. The generated files had a whole other extension, like .g.cs or something (it's been a while), and Visual Studio regenerated them on build. I eventually figured out how to use it effectively without any of the silly code generation magic, but it took effort to get it to not generate code.

ASP.NET MVC came with T4 templates for scaffolding out the controllers and views, which I also came to view as an anti-pattern. This stuff was in the official Microsoft tutorials. I'm really not sure why you think these weren't around?


That's lowercase "source generators". They're referring to title-case "C# Source Generators", an actual feature slowly replacing some uses of reflection.

https://devblogs.microsoft.com/dotnet/introducing-c-source-g...


    > 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.


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.


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.




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: