Hacker News new | ask | show | jobs
Thoughts on C# and .NET
46 points by mohaneds 1483 days ago
For those of you who have experience developing in C#, what are your thoughts on modern C# and .NET? If you have experience with ASP.NET Core, can you please provide some random thoughts about it? Did you or do you enjoy developing with C# or ASP.NET Core?
20 comments

I'm a full-time C#/.NET developer, and have been for about five years. Here are a few observations:

1) As time goes on, C# is increasingly just F# with different syntax. LINQ, closures, records/immutable structures, and so on - you can write code in C# that is more functional than it is object oriented these days. This is mostly a good thing; it also means that (to my eyes) the language has become much more elegant over time.

2) The level of cross-platform compatibility you can get if you're using .NET Core or .NET 5/6 is pretty fantastic. Performance has also improved dramatically. C# is in no way limited to Windows as an environment anymore - unless you have a dependency on .NET Framework.

3) Everybody has dependencies on .NET Framework. Those who don't have dependencies on unmanaged Windows DLLs.

4) Great debugging/developer tools, as others have mentioned. VS and Rider are both top-of-the-line IDEs.

5) C# is not and will never be cool, sexy, or the thing of the moment. It is a language for getting things done in an easy-to-comprehend way and that's just about all it is. I like it very much.

How realistic or common is it to mix C# and F# in one code base? As an example, even though Java and Scala run on the same JVM their ecosystems have mostly diverged. In contrast to the Scala situation F# has been officially supported by MSFT for years so it's probably different on the .NET.
The compiler won't build a project using multiple languages, it doesn't understand. For a long time, VB code and C# code were 100% interchangeable, and they of course, both compile down to IL. (Identical IL code, in theory, if literally the same code reflected in different languages.) In theory, you would imagine that you could include .cs and .vb files in a single project, and the compiler could link them, but alas, it is not possible.

There's no issues using a library written in one .NET language from another .NET language though, so if you can break the project up a bit more, you can work in multiple .NET languages.

The way to get around this is just to have multiple projects - at my employer, we regularly have projects written in C# and F# in the same solution, and depending on one another. That will work just fine.
You can also build a module instead of assembly (=default output of a project) but IDE support for modules is limited. After the modules are generated, an single assembly can be made from them.
I can't comment on the ASP/web side of things, but C# matches up with my atypical use cases extremely well. The high points:

1. Fast compile times for most of development. I'm fine with waiting a while to do a special highly optimized deployment, but getting 95-100% of optimal performance with 0-3 second build times is really nice.

2. Controllable memory access through value types. Nothing getting in the way of C-like contiguous buffers and managing cache line or load alignment.

3. GC that gets out of the way. C# has a GC, but in most of my applications the GC never has to run because I rarely allocate GC-managed instances. It's definitely a nonidiomatic use of C#, but the fact that it's pretty still pretty easy to do is nice. And when I don't have to care about the GC's overhead, the presence of the GC just makes everything easier developmentally.

4. Ability to actually make use of the hardware. The compiler's improved massively in the last several years, and the way vectorization is exposed actually feels a lot nicer than my experiences in C++-land.

And less relevant to my own needs, but still interesting, nowadays you can directly link a C# library into a C/C++/Rust application just like any C library. Among other things.

Almost a decade ago, I was considering exiting the ecosystem in favor of C++/D/not-yet-even-1.0 Rust/etc, but the open source push and a sudden focus on performance basically made the jump unnecessary. C# occupies a really nice sweet spot.

Yes! I suspect (but I don't know for sure) that C# has the highest performance sealing of all the VM-based languages with a tracing garbage collector. structs, Span<T> and ReadOnlySpan<T> for type-safe access to contiguous regions of memory allow us to do a lot without touching the heap. C# also has hardware intrinsics for non-cross platform SIMD instructions if you're interested in that kind of thing.
Perhaps it can be manually optimized the best indeed, but the JVM still has a better GC if I’m not mistaken.

Value types are indeed very cool, and I really hope they will get implemented sooner on the Java side as well. Just as a note, Java recently got a SIMD API as well.

You're right; Java has ZGC, a best-in-class garbage collector with average pause times of 50 microseconds while still having great throughput. https://malloc.se/blog/zgc-jdk16#:~:text=With%20concurrent%2...
Can you recommend any resource on how to get into writing high-performant C#?
Pretty much all the standard performance advice from other languages like C/C++/Rust applies. The concepts driving "data oriented design" will get you a long way. The biggest difference is learning how to play nice with the GC- avoiding allocations where necessary, keeping the heap simple so that collections are cheap, or just setting things up so the GC never has to run.

I wrote a bit of stuff a few years ago: https://www.bepuentertainment.com/blog/2018/8/30/modern-spee...

Some of the implementation details (like the operator codegen) are surprisingly outdated now given the speed at which the runtime has moved (and the library is now ~4x faster or something silly like that), but the fundamentals are still there.

Since I wrote that, codegen has improved a lot, the hardware intrinsic vector APIs have offered a way to optimize critical codepaths further, additional cross platform vectorization helpers have made it even easier, NativeAOT is getting close, and all sorts of other stuff. The C# discord #allow-unsafe-blocks channel contains a variety of denizens who might be able to offer more resources.

Thanks! That link especially contains some really helpful write-ups. I never really needed to think much about performance, as my C# usage always was constrained to simple CRUD stuff in server backends, which is a shame really, as it is such a nice language.
> Thoughts on modern C# and .NET

Pros:

- Excellent documentation and widely used (not just modern btw, MS documentation is world-class and has been for the ~8 years I've been exposed to .NET API documentation)

- Excellent code structure, supports very large/enterprise type code bases

- Mostly sane improvements, with regular rollouts

- Now cross-platform (although ref [0] for what it's like to develop in a non-Windows environment)

- IDE support is 5-star. MS own IDE is at least 4 star, and Rider (JetBrains) seems to be very popular as well. I've used three versions (pre-2019, 2019, and now 2022) at different paid levels, no big gripes. You get great debugging, profiling, REPL, etc.

- Mostly opinion/experience: Allows for a range of styles, while IMHO balancing correctly with consistency that is easy to pick up and collaborate with. It is possible to write confusing code, but not as much as other languages (ref [1]).

Cons:

- It is cross-platform according to the docs, but in reality it comes from and is relied on mostly in a Windows world.

- Testability - if you like unit testing, you will need to make everything testable also exposable - if not publicly, at least within the same assembly.

> If you have experience with ASP.NET Core...

Yes. It's pretty good. Apart from a question specific to the language or framework, you will need to quickly become familiar with how stuff works behind the scenes. It might also be beneficial to look into how deployment works, depending on your target (IIS, Azure, AWS, etc).

> Did you enjoy developing...

Yes, for larger projects that need collaboration. Otherwise I use Ruby.

[0]: https://news.ycombinator.com/item?id=31520399 [1]: https://www.ioccc.org/

* edited for formatting

Quibbles:

- Documentation: is ALL over the place. I do not find it excellent. There is LOTS of documentation but due to naming (.NET framework != .NET Core != .NET Standard != .NET [current]) you can find solutions that just don't work in the version you're in. Blogs are often the best source of details, but you have to have great search-foo to find the right one, and like stack overflow these won't age well (especially with the new rapid release cycle). There is documentation, and lots of it. *Warning*: On the MS official docs you can set the .NET version (with a drop down in the top left).. however if you try and visit something outside of that version, it will take you there and silently switch you to the last version where it was valid.

- Improvements: are great, they see what's happening in other languages and incorporate it. .NET might not be the first to have (although sometimes it is: `async`) but eventually gets the best solution to a problem (`Span` is rather like `slice` in golang).

- Cross platform: is very strong. Doing it the cross platform way is completely subject to the documentation trap above. A lot of the docs out there are for .NET Framework (the OG, which is not cross-platform) so you start off down the wrong path not realizing there's a cross platform way. This compounds pretty quickly.. if you're building cross platform, setup automated cross platform CI early.

- Testability: It seems you have not discovered `InternalsVisibleTo`[0], available as both a class-decorator or a project assembly attribute. It's been around since .NET Framework 2.0 (2005). Very much like the C++ `friend` declaration. You can allow a test assembly (only) to see private/protected/internal pieces of your assembly, if you desire.

- .NET (formerly .NET Core): Is great. Faster, leaner, and their future. Subject to the documentation trap above. Because of their rapid releases (and good comparability guarantees not forcing upgrades), it can be really hard to find good examples/docs of the newer releases.

[0]: https://docs.microsoft.com/en-us/dotnet/api/system.runtime.c...

" It is cross-platform according to the docs, but in reality it comes from and is relied on mostly in a Windows world."

Not my experience at all, everything I do at work is c# on containers running Linux.

Just on your point on Testability, I don’t believe this is the issue you make it out to be: my standard approach is to make all the types/methods I need to test ‘internal’ and then expose them to my unit test assembly using this assembly level attribute, https://docs.microsoft.com/en-us/dotnet/api/system.runtime.c...
I don't understand this logic. Test things that are public. You made them public for a reason. Those are what your class says it will do. Confirm it does what it says it will do and you're done with testing.

I'm not sure I've ever used the "internal" keyword in 12+ years of C#. Assemblies are about deployment; public/private is about the contract your code is agreeing to. They have nothing to do with each other, so internal is an inappropriate conflation of concepts, by definition.

Because all of our projects use Microsoft.Extensions.DependencyInjection, I make all our service classes internal

The way other projects can access these services is by using the project's helper to register them all in the DI container by their interface. That way I can be sure no one is directly using the classes and make breaking changes to the constructors (like swapping loggers or api clients), but still access them from tests.

And if you have two different DLLs that offer this pattern, but they overlap in the interfaces they register? The downstream consumer has no way of saying "I want my IFoo to be a FooA from assembly A, and my IBar to be a BarB from assembly B". And this may depend on a configuration setting! Real example: we have a bunch of useful Azure functionality in one DLL and a bunch of general useful web functionality in another; they implement many common interfaces and are alternative services you can pick depending on your deployment. But it's not as simple as "when on Azure, pick all the Azure stuff" -- it depends on which Azure services you've configured for that individual project.

If they overwrite each other, you might be able to get away with calling the god-registration-functions in the correct order (which you can determine only by reading the source code of the two functions, which are probably in different solutions), but if you really have a well mixed set of needs from A and B, this is probably not possible.

So you say: "okay, well, in this case maybe those concrete services should be public instead of internal. Obviously in this case we'd go back and do that." So your logic is: use "internal", except when it turns out we shouldn't have used it, then don't. So what purpose was it serving to begin with? None. The last lesson is that if you find yourself saying "in this case", you're actually talking about every case. That is to say: "special cases" are a symptom of a weak mental model. Good models don't have special cases.

So indeed we've identified several ways to improve, all stemming from the fact that you are using the "internal" keyword!

Wow, there really are people who just drive-by downvote anything that doesn't a-priori agree with their cargo cult mentality instead of stopping to think about it for 30 seconds? Of course they can't leave a comment, because that would require them to actually consider my point, which would put them in danger of actually learning something.

Downvoter, the problem with your downvote is that I am correct. DLL dependency/deployment is a completely separate concern from code contracts and visibility; therefore "internal" is always a mistake and should never have been added to the language. If you use "internal", we've identified that you have gaps in your understanding of software development that you have the opportunity to fill. But instead, go ahead and just downvote me again without comment. That's a lot easier than improving.

> if you like unit testing, you will need to make everything testable also exposable - if not publicly, at least within the same assembly.

One of us is doing testing very wrong then. Maybe I don't like "unit testing", if that means reaching into the internals of every class. Just test that your code is following the contract it says it's following.

The idea that you need to test private methods by running them out of context sounds to me like someone saying "A downside of this language is that you can't jump into the middle of a function to test individual lines of code. You can only run functions from the beginning." Yikes!

> Just test that your code is following the contract it says it's following

Usually testing output from a contract implementation works just fine. But sometimes my implementation contains bits abstracting parts of the whole. Those bits needs to be tested too, even though from an outside perspective there's no need to expose each bit. The consumer is only interested in the whole result.

A consumer of the contract may want to pass in a Dog object, and get a result that tells if it (the dog not the consumer) has fleas. My implementation will result in true or false, but I'd like to abstract and separately test the parts that checks if the dog itches, if it has tiny animals flying in a cloud around its head, etc.

I like good sane code coverage, but I also want it to be clear what is expected to be publicly consumed to drive the business logic, and what's just me making things tidy behind the scenes.

How did I end up in another conversation about unit testing?

Why test that seperately? You should be able to test any code from the public API.

Otherwise that code is unreachable and should be deleted.

After a long, intentional break break from the Microsoft ecosystem (more on this below), I recently kicked off a side project and was faced with the tech stack decision. After agonizing over Python vs Elixir, I landed on C#, unexpectedly. Reasons: static + strong typing is better for long-term maintenance IMO, extensive core library, solid database management, LINQ, excellent IDE, cross-platform support.

The stack is ASP.NET Core with .NET 6 on the backend, React on the frontend, Postgres as the database. Not in production yet, but planning to make a lot of use of AWS, particularly Lambda. I'm loving it, feeling very productive and trying to take advantage of many of the new language and framework features.

The biggest downside I've run into is that so much of the documentation and online material is geared towards older versions and older patterns. It makes sense given .NET's long history, but it's definitely been a journey trying to figure out modern best practices without ending up in an overarchitected mess (repository pattern everywhere!). The other thing I would say about C# is that I would never again interview with it, unless it was for a .NET position specifically. All the typing benefits work against it in a timed interview environment. Long time ago I picked Python up for interviews specifically and it was a much better tool for the "job".

Now for some context. I developed in .NET exclusively for ten years, from 1.1 to 3.5. The development environment was great, but over time the Microsoft ecosystem began to feel stifling. Everything had to be done the Microsoft way, if you step off the rails, you'd be in a world of pain. It was a mega monoculture spanning the entire stack, with high tolerance for throwing away previous standards (so much churn with Webforms/MVC, LinqToSQL/various Entity Framework versions, all the WPF/WCF stuff, and I'm sure much more since then). In the mean time the open-source world was moving fast, developing new patterns and technologies, with Microsoft attempting to follow, slowly. I left the ecosystem in 2014 vowing never to return. Yet... the .NET Core cross-platform push intrigued me and the ability to write C# purely for backend APIs that can be deployed outside of Windows felt like a win-win. Time will tell, but so far so good.

I love using C# to create games in unity as a way of keeping my love for programming alive. I dont know if its because its different from my daily job where i mostly write typescript and python as a dataengineer but I just really just enjoy the feeling of writing code in C# right now its just so refreshing.

Its refreshing to have types and have to say what the return type is without it being some type of pseudo enforced type as mypy or typescript. But as others have said, c# in the wild is most likely for big enterprise software which is not always the most fun thing to work on.

I have used C# for almost 21 years, so far. Written for .NET Compact Framework, ASP.NET 1.1+, WinForms, MSMQ, back-end server code, you name it.

Nothing beats this language for getting REAL work done in my books.

Tooling - Visual Studio absolutely unmatched. Core Lib/Type System - one of the best. ASP.NET Core - great rewrite of the old ASP.NET. Very fast. Runs everywhere.

C# as a language has evolved very, very nicely and I try to use the new feature whenever it makes sense.

I think the language is great, if you can tolerate windows, it's a great way to write in the same language desktop applications, CLI and websites, and with wasm even web clients. It's the right mix of performance and high level language for my taste, and the tooling (visual studio) is fantastic. The integration with Windows makes a lot of things easy within a windows ecosystem. Also the self contained nature of the binaries makes deployment a lot easier than say python (everything is xcopy compatible with little dependencies on the client).

However in term of recent evolution, I feel that under Anders Helsberg there was a focus on simplicity. Then Anders moved on, and anarchy followed, with multiple frameworks, multiple attempts to combine them into one framework, and ultimately the greatest of all sins for a language: breaking backward compatibility (.net 4.8 vs 6). And it feels everything is getting complicated, command line first, async everywhere which makes everything multithreaded, prone to deadlocks and hard to debug.

The original c# had a great set of core libraries, however it has now fossilised. I think the owners of C# feel it is not their job to "update the batteries". So for instance their drawing library still doesn't support HEIC and might never do, they only added a json serialiser recently (yaml probably never). Instead you need to rely on a myriad of third party libraries with version conflicts, dozens of assemblies, and god knows if they will be available or maintained 10 years from now.

So while I am heavily invested in C# myself, probably too much to switch now, if I had to start from scratch now, I don't know that C# would be my first choice. But the need to rewrite everything for .net 6 might give me the opportunity to do that however I really don't have the time for that now.

>I think the language is great, if you can tolerate windows

I do most of my C# development on Linux. Not sure why you need to tolerate Windows to enjoy C#.

Because you are missing out on the desktop UI capabilities of the language which is a great feature at least if you use it as a hobbyist like me.

And Visual Studio. To me the IDE matters as much as the syntax of the language. And when I was a beginner having an IDE that can interact with my code as I type it is a massive help to progressing.

VS Code, while not a fully-fledged IDE, is really good at suggesting code completions across platforms. As others have mentioned, Rider is also a wonderful fully cross-platform IDE (better than Visual Studio at some things).
Rider runs on Linux too
I worked on a (failed, explanation follows) project to trial port a large system from .NET Framework 4.6 or 4.8 to I think .NET Core 2.1 or 3.0. I was most annoyed at having to recreate project files, but it was quite straightforward and easy, even for us, where we had complications like numerous sub-projects, some generated code (luckily, I quickly found the correct arcane incantation embedded in some GitHub issue), etc. We also had to rewrite a C# API wrapper for a C library we used for reliable multicast, because our vendor had disappeared, and their wrapper was shipped as a Windows-only Framework-only DLL (and honestly, the code quality was relatively poor, with a lot of needless complexity in their wrapper). But we had already consciously chosen to only use sensible slices of that library’s functionality, so writing our own wrapper was quite trivial, even though I hadn’t used C#’s DllImport stuff before. It was easy to port the slice needed for most publishers first, before having to port the slice for receivers, etc.

All in all, I’d say the port for our large suite of server apps, with lots of different CPU/memory bound and network I/O bound apps, it was quite straightforward and easy.

What killed it in the end for us, was that our testing showed our CPU/memory bound apps ran 0.9x as fast on Linux .NET Core 3.0 vs. Windows .NET Framework 4.6/4.8 OR .NET Core 3.0, using near-identical hardware. The Linux port just wasn’t there yet, so we decided not to go through with any port to .NET Core for any part of our system, since Linux would have been the main benefit (we were using fancy network cards and stacks, and the Linux support for those was far more advanced and reliable compared to Windows, where multicast groups would spontaneously fail to be joined, etc.).

Another elephant in the room, is that GUI support on .NET Core was limited at the time, so we may not have been able to do a Big Bang for that reason also. We had a few WPF apps, and some Forms stuff, but I have no clue about what .NET Core support was/is for that stuff, as I rarely worked on GUI things (I would create a GUI as a necessary part of an experiment and hand it off to more experience people, or modify/simplify GUI logic as part of my evolution of our server stack, but that’s all).

So obviously YMMV and it seemingly does vary, but my experience on server apps that weren’t using lots of Windows/MS specific technologies (I’m not a fan of them generally), our porting was really quite easy. Still a failure though :-)

The performance improvements between .NET Core (any version) and (just) .NET 5+ have been pretty astounding. I've seen some incredible micro-benchmarks where the same code running in .NET 5+ hugely outperforms anything running in a version <= 4 (both .NET Core and the old .NET Framework). Factor in what you can do in memory-bound code by rewriting it to use .NET 5+ things like Span<T> and stackalloc there's no question that at this point you should be able to get much better performance out of .NET 5+. If performance was your bottleneck in .NET Core, the latest .NET versions (6 [LTS] or 7 [Preview]) may be worth checking out.

GUI stuff is also what happened in .NET 5+. WinForms and WPF were both added to .NET 5. There's some porting that needs to happen and not all old WinForms/WPF code will be happy on .NET 5+. The biggest remaining "missing piece" from a GUI perspective is that a lot of old WinForms/WPF code used WCF for service code. I think porting WCF to use simpler REST services is easy enough and there's lots of people doing that with gRPC services in .NET 5+ too. There's also the Open Source CoreWCF project that is trying to hit compatibility with the most used bindings in WCF on top of .NET 5+.

The relevant part were our model calculations and it was already heavily optimized (no allocations, etc.), so none of the new library stuff would help at all. We just found that Linux .NET Core underperformed compared to Windows .NET anything (Framework or Core). Maybe our methodology was wrong, but we had a few people look at it, made the comparison in several ways, and generally tried to be quite thorough. We always found Linux (only .NET Core) had ~0.9x the performance of Windows (any .NET) — and we were a mostly Linux shop, running C/C++ apps, so our Linux build was appropriately tuned. Given .NET Core was relatively new at the time, and Framework still had some legs, we didn’t dig down as far as looking at IR or JIT output, because we could afford to wait. After all, one of the draws was to reduce problems due to using our fancy network cards+stack in Windows, so there was no point in adding complexity from a new Linux .NET runtime, including complexity from having to troubleshoot performance.

I’ve been saying “we”, but I left that job a couple years ago so I won’t be trying any new .NET versions myself :-)

One of the issues I ran into when I looked at porting to .net core 3 was the crypto library. They didn't implement all .net framework classes, the ones related to public/private keys in particular. Perhaps they do now. I don't know how many edge cases I will find like that.

On the recreating project files, I don't think you can copy-paste winform code like that, you need to recreate it using the UI, which is a pain. Also they had this weird concept that an assembly had to be set to be a "winform" or "wpf" assembly, you couldn't simply reference system.windows.forms, which basically prevented you from creating a helper winform assembly, and you certainly couldn't create an assembly that referenced both winforms and wpf DLLs. They perhaps also fixed that since.

In any case, it's not a complete rewrite to migrate to .net 6, but it is far far from a simple lift and shift.

I’m not picking on you specifically, it sounds like you do a lot of desktop .NET development and there are legitimate complaints there.

However, “too invested to switch now” reminds me of .NET/C# developers that don’t embrace change and/or don’t swim in other waters.

Some folks with a traditional Microsoft/Windows/.NET Framework mindset have real trouble with these things:

>“Windows and Visual Studio”

Rider, Linux, Docker, bash terminals, and MacOS are all things that can improve your ability to solve problems with .NET. I’ve met dozens of people that refused to even try a different OS/IDE and fail to gain the benefits of those tools and the broadening effect they have on your perspective.

>”too many frameworks”

This complaint sometimes stems from an unwillingness to learn web/mobile frontend technologies. On the server-side, ASP.NET continues to simplify— you can write an API now in one file with a handful of lines of code. [1]

>“limited core libraries”

The first party support in .NET is already pretty comprehensive. Some folks have an aversion to using open source packages, but this is exactly how you get things like YAML parsing in most other languages. Microsoft’s move to open source has been awesome. You can literally interact & contribute with the team’s that make the framework.

>”command line first”

This sounds like a feature to me. This allows for automation and is easier to document without a series of screenshots. Having a common CLI allows a team to mix their preferences of IDEs/tools.

>”async everywhere which makes everything multithreaded, prone to deadlocks and hard to debug”

I disagree. async/await makes it easier to write readable code without deadlocks or many of the other pitfalls of multithreaded programming. It is not inherently multithreaded either [2].

Since it’s ‘everywhere’ and thus fundamental to writing C#, it’s my first interview screening question. A large number of candidates, with a lengthy .NET background fail to articulate any issues with calling “.Result” or “async void FunctionHere()”. IMO, you have to deliberately avoid understanding such a ubiquitous language feature.

[1] https://docs.microsoft.com/en-us/aspnet/core/fundamentals/mi... [2] https://blog.stephencleary.com/2013/11/there-is-no-thread.ht...

> However, “too invested to switch now” reminds me of .NET/C# developers that don’t embrace change and/or don’t swim in other waters.

Or I have other things to do and I don't want to have to relearn and rebuild everything for no obvious benefit. I have no fascination for the tool, I am keen on what I can do with the tool. Having to rewrite something I already did because someone decided to change the internal architecture of a core part of the framework is a waste of my time. The .net team used to understand that.

> This complaint sometimes stems from an unwillingness to learn web/mobile frontend technologies. On the server-side, ASP.NET continues to simplify— you can write an API now in one file with a handful of lines of code.

that may be but compare the amount of time it takes to drag and drop a button on a winform, double click on it and write your code vs doing the same thing web based. I also do websites, but the time invested to do a simple UI is an order of magnitude what it takes with a desktop app, so I need to have a really good reason to go web based for a home or internal project (and now I need to deal with a server with certificates, etc). And there are many applications where it is simply not possible. Interacting with files, or visualising non h264/aac videos, selenium, etc.

> This sounds like a feature to me

I am not saying it is not intended, I am saying it is more complicated.

> It is not inherently multithreaded either

Yes it is. You need to constantly think whether you are going to come back on the same thread or not. Something that works fine on a console application or within an exceldna formula will deadlock on a asp.net or wpf application. And it breaks the call stack when you get an exception, making it harder to debug. I think I understand this feature reasonably well, thank you.

And not just multithreaded, asynchronous also. That means if a user clicks a button and that button has async code, the user can do more interactions with the UI while the original method is running. Now you need to handle many more UI states than you had without async.

I’ve been a C# developer since the beta. I started off mostly WinForms, then moved along to asp.net, then MVC, now Blazor. I don’t have anything specific to say. It’s a perfectly reasonable language, standard library, community, etc. I have never been frustrated by it, and have enjoyed 15+ years of continual improvements. I think knowing what your specific questions are would be helpful.
C# is great and improving. A well designed and very pragmatic language. The dotnet core changes cleaned things up and made it feasible to build and deploy cross platform apps. I mostly use it for Unity and server code.

I hope the AOT story continues to evolve and come down in size so I can start writing small utilities in it.

.NET Core and C# are an awesome way to program a plethora of apps and services.

The bloat is a bit much, but once you get used to it, its a damn good eco-system.

Bloat is also being rapidly shed with many improvements. .NET 7 (the upcoming version) is going to be even better.

The programming and architecture practices recommended by MS are great for most apps.

I had an internship where we worked with C# and .NET, and recently i inherited and application written on this stack. I would say I much rather would develop in Rails.

Why?

Currently, I don't have a ton of time to maintain the app and I was hoping to migrate it off of aws (since I don't have a ton of aws experience and mostly don't want to mess with it/ manage it) so I was looking to deploy on heroku which doesn't support C# as of now. - https://help.heroku.com/PAT3YEDU/does-heroku-support-net-app...

If you get past my gripe, I think it's much harder for new developer to pick up.

I think Ruby/Rails and Python/Django were created specifically for developer happiness and ease respectively.

I don't think it's going to be easy to manage this asp app (even though it isn't a large code base) Simply due to the fact that there is a lot of stuff I feel like I'll need to catch up on. When I look at other apps written for the web I find them to be "easier", whatever that means.

The third party buildpacks work totally fine.
Oh sweet, thanks bpicolo, I hadn't thought about that! I'll check that out.
Working with ASP.NET Core full time (on a Mac with Rider), for me it is the most productive language/framework that I ever used.
The bad:

- Web framework is not quite on-par with Django/Rails. Particularly with auth/OIDC.

- Job market is very sector dependent. Startups just don't seem to use C# whereas big enterprises do.

- Some design decisions in the early days seem to prevent some features being added, e.g. structural typing or union types (not certain this is the reason).

- Some days, like yesterday, your IDE updates and gets messed up and then it's back to writing full variable names like a medieval monk.

- .NET is not really viable for non-server work. There is embedded/desktop stuff but you're going to have a headache outside of servers.

The good:

- When it's working properly the developer experience is like those dreams where you're flying. Rider or VS + Resharper make typing anything seems an incredibly antiquated way of developing. Intellisense means you barely ever need to type more than 2 characters to express your intent. Unlike my experience in Go or Python I just think about intent/data shapes and the tools fill in the rest. No language I've ever worked in comes close to reducing the ideas -> implementation effort barrier.

- Type system. This is non-negotiable for me, plenty of syntactic sugar to make overhead of typing lower but because it's compulsory, types save so much mental effort and our production systems never wake us up, feeds into the above. You don't just have to reimplement a compiler badly through unit testing saving a lot of time.

- Tooling, full framework used to be hell and very tied to the (paid) tools. Now with Core the tooling is great and Linux support is fantastic.

Overall I don't want to work in any other language if I can stick to C# but the fact startups don't use it mean I might have to enter scripting language hell, again.

>- Web framework is not quite on-par with Django/Rails. Particularly with auth/OIDC.

Completely wrong. Not only does Auth work particularly well out of the box, but microsoft documentation is top tier with both c# and asp net core.

https://docs.microsoft.com/en-us/aspnet/core/security/authen...

asp net core is the single best framework for backend web at this time, but yes, won't be as fast to ship as django/rails. If you like to maintain code, I'd consider it a worthy tradeoff.

Ah, fair criticism, thanks for educating me. To be honest I'm repeating barely-remembered experience from years ago now, time flies. I just always found documentation pushing to Azure AD, or the now paid IdentityServer stuff, when it comes to SSO.

The other thing that works against me in having a Django-like experience with web is I always switch away from EntityFramework as early as possible in favour of handwritten SQl. I think a fair comparison probably puts EF level with or ahead of Django.

But this auth documentation looks interesting and definitely an improvement.

Their offical "SPA API Auth" involves paying for a third party OAuth server (called Duende IdentityServer, the ASP.NET Core built-in auth solution on the other hand is called ASP.NET Core Identity) and Microsoft refused to ship their own server because that would be "competing with the open source community" (there are multiple long Github threads on this which Microsoft product people would lock and mute). I wonder which PM got kickbacks for that decision. Originally Duende IdentityServer was free under Apache, and then they immediately turned into a paid solution after they became the "official" solution for API Auth. Microsoft also claims that standard cookie auth cannot be used for API Auth in a SPA scenario (their security engineers insist that you use JWTs), if you look through their Github issue threads, the ASP.NET Core engineers seem to believe that the default ASP.NET Core Identity generic cookie auth is for MVC only and it is "not recommended" for use with SPAs.

https://docs.microsoft.com/en-us/aspnet/core/security/authen...

https://duendesoftware.com/products/identityserver

They like to hide their dirty laundry behind clever developer advocacy teams and drowning it in Github bureaucracy but here are some examples:

https://github.com/dotnet/AspNetCore.Docs/issues/25832

https://github.com/dotnet/AspNetCore.Docs/issues/7644#issuec...

https://github.com/dotnet/AspNetCore.Docs/issues/24157

https://github.com/dotnet/AspNetCore.Docs/issues/18524

Their PMs are not building an open source product in good faith. As far as they are concerned, ASP.NET Core primarily exists as a funnel to send customers into Azure or buy stuff from Microsoft partner companies. Imagine if Vercel one day removes the standalone server hosting option and requires you to pay for their serverless cloud to use important features. This is what Microsoft is doing here right now with ASP.NET Core. If you lock your stack into their ecosystem, then be prepared to spend money either on redundant cloud subscriptions or developers reinventing the wheel on core features

It's not Microsoft's fault that Duende took their ball and went home, becoming a closed source operation after years of open source work. That's on Duende for being bad Open Source community members.

I somewhat sympathize that Duende's unpaid support costs went up once Microsoft pointed a lot of heat their way by including it in official samples, and they should get paid for support. I also somewhat sympathize that closing their source was seen as the easiest option to redirect the community to paid support plans.

But it's still a jerk move in the Open Source community to have code be open source for more than a decade and then close it simply because it was used in one tutorial/sample too many.

Sure, Microsoft could have offered sponsorships or other help, had Duende asked. Supposedly Duende didn't ask, their first public response was when they immediately went closed sourced because apparently they never really cared about Open Source.

There are people not building an open source product in good faith in this story, but it doesn't seem to be Microsoft (surprisingly).

Microsoft probably should replace IdentityServer with something that actually wants to be good Open Source in the samples again. They are between a rock and a hard place because if they fork the last Apache versions of IdentityServer they look like the bad guy for "stealing" Duende's work at that point, and they can't resurrect their old code because it was bad. They probably have to wait for some third party fed up enough with paying Duende for bad faith Open Source to make their own fork.

They don't have to fork anything, they can just add a built-in OAuth to ASP.NET Core Identity. They did that with JSON and created an in-house alternative to Newtonsoft's fast JSON library once it became popular enough. I didn't hear any PMs whining about "competing with the open source community" then.
OAuth is too (stupidly) complicated for "just add a built-in OAuth to ASP.NET Core Identity". They had that in previous samples (which you can still find online if you look) and it was bad code that suffered from code rot and stopped being "OAuth compliant" especially with respect to even more (stupidly) complicated OIDC, so they looked for someone to maintain that and IdentityServer already existed and was OAuth compliant/audited. That's why it needs to be forked. Someone has to do the OAuth/OIDC hard work. Microsoft is paid to do that in other divisions of the company, of course, but it's all in Azure and they want you to pay for Azure AD alphabet soup that pays the bills for their OAuth/OIDC compatibility auditing.

> They did that with JSON and created an in-house alternative to Newtonsoft's fast JSON library once it became popular enough. I didn't hear any PMs whining about "competing with the open source community" then.

James Newton-King, the "Newton of Newtonsoft", was one of the developers involved in that project that decided a clean compatibility break with JSON.NET (which has been showing its age, but also has far too many users to easily break API compatibility) was necessary to achieve the performance goals that System.Text.Json was created to meet. That's not so much competition as it is cooperation.

Cookies work great for SPAs and I highly recommend the path for first party auth. Setting up CSRF tokens on the frontend is a lot easier than setting up OIDC.
That's correct. And if you have configured SameSite corrrectly and your GET request handlers are locked down, you don't event need CSRF tokens. Unfortunately Microsoft ASP.NET Core security engineers' careers seem to depend on have differing opinions on how to secure SPAs and APIs.
> Intellisense means you barely ever need to type more than 2 characters to express your intent.

Agreed Intellisense is pretty good. Not excellent - it never suggests using statements as a first option, and in unit tests I have to always type "Asser" to bring up "Assert" (using 2022 expensive version). But it pretty good.

From a tooling perspective, this a positive. From a language perspective, well there are other languages that allow flow-of-thought without needing the tooling to autofill [0]. There are tradeoffs, and practically speaking you'll need to eventually look at someone else's flow of thought and expect them to understand yours.

[0] https://www.wisdomandwonder.com/article/10231/if-you-like-li... "...by and large, I find that the language allows me to express myself, in the language, about the things I’m dealing with. I find that a very precious property of a programming language."

> - .NET is not really viable for non-server work. There is embedded/desktop stuff but you're going to have a headache outside of servers.

As an outsider to this ecosystem, desktop development is one of the things that has me interested in C# at all (built in windows stuff + Avalonia + Xamarin and the like).

Can you elaborate this?

So I think the current/new desktop approach is MAUI, maybe, it's hard to keep track. When evaluating Microsoft/.NET stuff I think the safest approach is to ignore anything they come out with and check back in 4 years to see if it still exists. Desktop is the worst for this.

Winforms and WPF are still solid choices if you need a Windows only UI and I think Avalonia is still going and might be good, I haven't really had an opportunity to evaluate it. But I think desktop still lacks a compelling cross-platform solution post-WPF and adopting early solutions from Microsoft will cause you many headaches and much sadness.

MAUI is the continuation/rebrand of Xamarin, most especially Xamarin Forms (as some of what used to be Xamarin is now just baked into .NET at lower levels). It's still more cross-platform mobile focused (iOS and Android seem to be the best tested platforms), though with support for full WinUI 3 and macOS Catalyst the lines are blurrier than ever between mobile and desktop development, especially if you are trying to write a cross-platform app. The biggest missing link for cross-platform desktop development in MAUI is Linux. (There's at least one open source fork working on a GtkSharp implementation/backend for Linux, but I don't know the ETA on that shipping.)
IMHO, you only know a .NET platform is there to stay when they finally get around to adding VB support to it. WinForms? Solid. WPF? Here forever. UWP? Never going to go away.

.NET MAUI? Who the heck knows, it's still C# only.

MAUI on Windows desktop is just a cross-platform wrapper around WinUI 3 (UWP's UI replacement) on top of WPF. Worst case, if you are building Windows Desktop apps, you eject back to WPF. [0] For the rest of the platforms you could switch to Avalonia or Uno perhaps.

[0] Though of course the question then is that if you are building WinUI 3 apps and don't need cross-platform why you don't just use WPF directly, and I don't have a good answer there. I think MAUI might be slightly more appealing if there was a stronger bridge from "start with a WPF app then expand it to MAUI". (You can do it, in some ways it is straightforward, but it's not an official garden path.)

(ETA: Though I get you on the VB.NET as gate idea. It's a big reason I still don't entirely trust Razor much less all the hype around Blazor. Blazor more than anything feels way too much "for people that missed Silverlight, here's a worse ASP.NET Core Silverlight" with shades of ASP Classic runat="server" full circle)

This is poetry! Spot on.
Early comment in the thread but I wonder if the overall “zeitgeist” opinion of C#/.NET/Microsoft has changed. Used to be in /. days that a lot of people stayed away from Microsoft stuff, preferring Python, Ruby. Then Stack Overflow came and things slowly started shifting.

I do internal dev tooling in C#. It’s one of the best languages out there for raw “get shit done” prowess. I’ve used many languages throughout the decades, some more elegant than others. Like others, I’m a bit wary of the explosion of syntax shortcuts and alternatives, but whatever, its no big deal. It’s a great all-around, general purpose language. I would love to use F# more but I don’t think some decisions they’ve made would go over well with many people.

At least looking at job boards, there's a problem with .NET mostly being stuck in its own stack with little overlap while many jobs existing scoff at the idea of e.g. letting Java and .NET devs jump from one to another. Which leaves .NET in a weird position of having to go all-in, even if it has a high market share.

Without that stigma it's fine, .NET has a lot of answers to various things even if you have to deal with the Microsoft flavor. Similar to Java which still dominates most of the market in the same space.

> I don’t think some decisions they’ve made would go over well with many people

I'd love to know more about what you think and are referring to.

I've been developing with C# professionally since 2012. I'm using dotnet core since the 3.1 release.

Pros:

- Superb IDE. Nowadays even without Resharper, which was a must have in my early C# days.

- Massive standard library and extensive documentation makes for a very productive development experience (especially backends).

- dotnet core was a huge leap forward in terms of dev tooling (sane cli, 3rd party integration) and framework flexibility (configurability in general, config by code instead of XML)

- Microsoft keeps adding language features copied from functional programming languages.

Cons:

- Enterprise culture is very pattern-centric and produces much boiler plate code. That's the main reason I want to move away from dotnet.

- Desktop apps are not cross-platform and I don't trust their newer stuff to deliver on that promise. The older WPF is outdated (e.g. no inbuilt support for "newer" features like async/await) because of their focus on Xamarin and MAUI or whatever new fad they come up with.

- Microsoft seems to swallow open source libs by integrating them or copies of them (e.g. Newtonsoft.Json) into the standard lib. I'm not sure if this is a good thing. I know I'm contradicting the "massive standard lib" pro from above. But here I'm thinking long-term.

- New language features are sometimes done half-assed for backwards compatibility. E.g. nullable reference types that are just syntax sugar instead of a proper maybe monad with language integration.

I think the absorbing into .NET behavior of their standard library is the standout reason to use .NET: You aren't going to have to shim basic functionality out to some random third party who might or might not abandon the code, and could inject malware or telemetry down the road without telling you.

The .NET approach of ensuring you can do nearly everything an app might need basic functionality-wise in the standard library probably sets .NET as one of the most secure platforms to develop with.

It's the anti-NPM, and NPM is terrible for security.

It's really hard to get an objective view of a lang asking for a random thought :-) C# has a decent number of fans acquired because of Intellisense mostly, there's also not an insignificant amount of people who find it "meh" (boring, Microsoft etc.) If you are asking to evaluate it as an investment - it seems to be a pretty solid choice, though employability of a C# dev differs between locations. If it's just for fun/pet project, then it's better just to give it a try.
I love it, but then I've been doing C# since the beginning. The language does appear to show its age though. I recently tried teaching it to my kid and realized that it has grown quite a bit of cruft.

There are a ridiculous amounts of ways to declare a variable.

    Employee employee = new Employee();
    var employee = new Employee();
    Employee employee = new();
And that doesn't even count the initializers {}, records, dynamic, etc... All this makes it difficult for a new develop to jump into it.
The first two are not necessarily equivalent. If your class implements an interface you can type the variable as this interface and have different behavior if the interface methods were declared explicitly [1]. You can also use it to interact with for-each loops and some legacy (2.0 and prior) APIs and save yourself an explicit cast.

The last one is a very recently added shorthand which, honestly, I think was just implemented because an intern needed a feature on master to get hired or something like that... I don't get it either.

1: https://docs.microsoft.com/en-us/dotnet/csharp/programming-g...

The last one is great for field initializers where you can't use var.
The first way is no longer recommended. You are encouraged to only use the latter two for readability (except for the interface object type situation mentioned in the other thread).

> The language does appear to show its age though.

Come to Java land. It takes decades to add a single feature. Grass is always greener.

Just wanted to add... C# being strongly typed language, some times can be a pain. Just spent an hour trying to parse a JSON String; changing its parts and again de-serializing it... working with JObject, JToken, etc..so on; Though i'm just getting familiar with it.
This is where the C# `dynamic` keyword sometimes shines. It's a lesser known feature from a project called the DLR that tried to shake up the CLR to better support more languages like IronPython and IronRuby. Both IronPython and IronRuby kind of got scrapped/pushed aside, and the DLR never quite got the love it deserved, but what remains is still sometimes useful. If you are still using JSON.NET (Newtonsoft.JSON) (which you did mention its JSON LINQ JObject/JToken, etc.), it still supports the `dynamic` operator for working with JSON "more naturally":

  dynamic jsonObject = JObject.Parse(someJsonString);
  jsonObject.some.deep[1].propertyName = "Updated string";
  return jsonObject.ToString(); // re-serialize the updated JSON
You don't get much IntelliSense when using dynamic, but it makes it much easier to write what you want/expect to write with a JSON object than if you were navigating the JSON LINQ (JObject/JToken/etc) objects by hand.

The documentation still includes pages on dynamic, even though dynamic has become a mostly forgotten feature and there are still developers that would never use dynamic. For example: https://www.newtonsoft.com/json/help/html/QueryJsonDynamic.h...

I appreciate that dynamic exists in C#. I've done some wild things with dynamic over the years. It sometimes makes me sad how under-appreciated a tool it can be.

ETA: Obviously dynamic is not type-safe, it's a great escape hatch from type safety for small cases where "just do what I want" is nicer/more natural than type safety. As the sibling comment points out, if you still prefer type safety there are type deserializers that can be easier to use. These days with records in C# 9+ writing quick types to deserialize to is even relatively painless.

You should be able to simply use Json.Deserialize<T>() ?

The beauty of types in this case is that the JSON structure has a natural way to be expressed in C#… we just need to utilize it

Or a JsonNode if you don’t know the structure

If you are using VS try the JSON to Class converter that's built in.

https://www.howtosolutions.net/2020/07/visual-studio-creatin...

Modern C# is a decent language.

But legacy projects are still everywhere, and not only are they frustrating because outdated language version, but Microsoft is actively removing downloads for old .NET versions I need to maintain those legacy projects.

Comfortable language, great ecosystem. But it’s Microsoft Java, so to make a career out of C# inevitably means boring enterprise work.

There are exceptions like Game Development of course.