Somtimes you need to space away from a thing to realise how much it has been weighing on you. I've been writing C# in one form or another, almost every employed month since mid 2011. My most recent job has me writing Go instead, and it's honestly been a deep breath of fresh air. I don't know how much of that is sheer overexposure mixing poorly with mmy ADHD, and how much is C# itself, but getting away from it for the past 6 weeks has been so nice.
The Stupendium has a line: "For a city is its people and it’s people are it’s heart" . If a language is its libraries, then the standard libraries are the heart of the language. And the standard libraries for C# are nice. There is a lot of cool stuff in there: a regex engine that lets you set execution timeouts, the Task Parallel Library, all of the cool things that comprise LINQ, and more. Once you get past the inner core of data structures and computation management, though, you run into the corporate sprawl of Microsoft frameworks. Some are merely old, but still reliable. Other are preppy, keen, and maybe too eager to please. Others still would put Ikea to shame as eldritch entities in their kafakesque windings.
WinForms might be the framework that I dislike going back to the least? It's just about the highest polish wrapper around Win32 I'm aware of 1, and it largely doesn't go to terribly far beyond providing a UI toolkit and providing some means to connect to various databases. It has some pitfalls, of course, perhaps the stickest being that it uses GDI+ for rendering, which is said to put an upper limit on how fast said rendering can happen.
-WebForms-, on the other hand, is maybe Microsoft's most misbegotten moppet mucking in .NET. An unholy chimera of server-side logic and AJAX calls to swap out parts of a page, which awkwardly cloaks HTTP's request/response nature via opaque page lifecycle events, even for partial page refreshes, WebForms is one of the most mismatched abstractions I've seen. There was an attempt to make a visual designer for the thing, akin to the WinForms UI builder, but HTML doesn't fit the attempted experience, so, if you, someone who doesn't know HTML, tries to use that designer, you get left with the frustration of wondering why the The Thing won't go where you want it to, not knowing that you're fighting a proxy war with CSS and the box model. Depending on how you want to think about it, it was before its time, or it was the product of a corporation attempting a cthonic art far beyond its ken. You would bind data? First, unbind yourself from the WebFen.
The one tolerable approach to WebForms that I've seen is to basically scrap the page lifecycle and the controls, and to use it as a "We Have PHP At Home" , and build your code atop .aspx page templating and @Response_Write, reaching into the Request and Response objects. This basically throws out a lot of the effort MS but into WebForms, but it does leave you with something more honest and less Meetings in the Backrooms that should have been Normal Emails.
Funnily enough, Microsoft had, in their own way, two versions of that same idea, ASP.NET MVC, and ASP.NET Razor Pages. Razor Pages are maybe the least Extra Nonsense in a Microsoft framework, because it has rather humble aspirations. There's a nifty little syntax (the eponymous Razor) for letting you use basic inline control flow to emit HTML and insert data, some ability to chunk markup into sections that can be used by other pages, and a means of writing a subset C# inline. Page routing is but a mirror of your source code file tree. Ironically, one of the parts of .NET I've seen used the least, but decently pleasant.
ASP.NET MVC. Hoo boy, where do I start? On one hand, it's a kinda a technical marvel, providing APIs, views (either aspx or Razor, coder's choice), a service-locator/depenency-injection container, pluggable middleware, all the Web Framework Features you might want and more. You can write controllers by just subclassing Controller and adding some attributes to your methods. Query string values get automagically supplied to your method parameters. You can register lil functions in the setup to provide services to the controlls that ask for them, in your constructor paramters, of course. We don't do property injection in this shop. See ma, no manual route registration!
But then, you have to live in codebases that seep out of that feature set. Want to find what code a given HTTP path resolves to? Better hope your code search skills are up to snuff, and that you're not getting 404s because some piece of your request didn't match the set of routing constraints ASP.NET constructed from reflecting implications from your code structure. You wanted a way to list out all of the routes that -could- match? RIP bozo, make sure you supplied values for all of the parameters to the method you're trying to hit. Your routes list never got high enough in Microsoft's ticket backlog, or is, for some reason, impractical at either a computation or design level. Also, dependency injection is there for a couple reasons, but the majority of the time, it's there to make your controllers testable. For a lot of .NET shops, that means that every injectable dependency is spread across a -minimum- of three files: One for the Interface, one for the Test Stub, and one for the actual, concrete implementation. Of course you can use Moq and lose the Test Stub. You can also mark the mockable parts of your class as virtual, and then lose the interface, but the person who taught me that trick is literally the best .NET dev I've worked with, and nobody else outside of his direct sphere of influence I've met has been aware of that. Dependency injection can also lead to classes that literally have dozens of constructor parameters. What's one more, just throw it on the pile!
Windows Presentation Foundation is another mixed bag of a framework. Yay, it uses DirectX instead of GDI+, so the rendering can be nice and fast and effcient. Boo, it has gaps in functionality due to not being a straight Win32 wrapper. Yay, it has a nifty DSL for describing UI layouts! Boo, data binding is weird and fiddly and has 5 concepts in play, which is 3 too many. Yay, it has nice gridding capabilities! Boo, describing that grid in XAML takes a bunch of nodes. Yay, it enables separation of concerns! Boo, all the docs assume that a given WPF app has at least 3 differnt roles involved in building it!
To be honest, the practice of putting every class in its own file might be one of the biggest friction points I have with C# as a culture. It's not a requirement imposed by the language in -any- sense. You can straight up dump multiple namespaces into a single file, filled with classes and structs and, these days, top-level methods. You can also shotgun a single class across multiple files using partial classes (a feature I am sure was heavily motivated by WinForm's UI builder). C#, as a set of programming langauge features has a deep and broad set of useful code organization techniques that only grows deeper and broader as the language gains version counts. I've seen people take C# controllers and use them to generate both OpenAPI specs, and then also HTTP clients in Typescript, all using Roslyn's code analysis tooling. Hell, a full C# program can basically be two files, a code file, and a project file to say what version of C# the code file is using.
But almost nobody writes C# that way. In the face all of this flexiblity, the "new class" button in Visual Studio creates a new file. Namespaces follow folder hierarchies by default. public static void main and all of its progeny haunt C# programs like oil spilled in a stagnant bog, even as alternatives and shortcuts accure, courtesy of a language team that chases ever more ease of development, building ever more ramps into the pit of success. Did you know that using statements (the scoped resource thing, not the namespace import thing) in recent versions of C# no longer have to be followed by a code block, that they'll just clean up when you leave the method? Did you know that C# has introduced nullability analysis that'll tell you if you forgot to check a nullable value? That Span has been created to reduce the amount of allocation it takes to work with strings for things like parsers? And yet, much of the C# code I've run into still has quite a few layers of -stuff- around it, retroactive accidental complexity.
I've had periods in the past where I have straight up made DSLs for generating C# code because of being tired of typing out `public override string myProperty { get; set; }` even if the IDE in question can help autocomplete decent chunks of it. To be honest, that... undue metaprogramming is one of the stronger signs I've seen in myself of being burnt out. It's been a bit over a year since I was at that point, but it sits in my head. I don't know how much this is sour grapes and exhaustion. But getting space from it all has been quite nice.
[1]: Delphi & Pascal devs are welcome to correct me , I lack proper experience there, but Pascal also isn't my jam