Hacker News new | past | comments | ask | show | jobs | submit login
Learning C# and .NET after two decades of programming (kerrick.blog)
60 points by Kerrick 1 day ago | hide | past | favorite | 75 comments





I’ve been working in C# since 2002 and only in the last 2 years have I been using the Rider IDE and will never go back to VS Code. I know most C# developers use Code or Visual Studio but Rider has a lot of small features that make working with the c# very enjoyable. I have no affiliation with JetBrains (maker of rider).

I used JetBrains for Java (IntelliJ IDEA), Python (PyCharm) and Javascript (WebStorm) and many of the same affordances work across all those languages and I'd expect Rider to do the same.

(Funny I am doing a hackathon this weekend where we are writing a game with Unity and I'm using Visual Studio for the first time in 15 years! It's pretty amazing how big a role C# plays in game development as it is used in many other frameworks like Godot)


Rider is up there with IntelliJ IDEA in its usefulness. If you opt for the package of all of JetBrains' tools, then it's definitely worth a look, IMO. Of course, the full Visual Studio is also pretty nice nowadays, I'll admit that.

As for game engines that support C#/.NET/Mono, there's actually quite a few:

Unity https://docs.unity3d.com/6000.0/Documentation/Manual/scripti...

Godot https://docs.godotengine.org/en/stable/tutorials/scripting/c...

Stride (formerly Xenko) https://doc.stride3d.net/latest/en/manual/scripts/index.html (even the new physics engine they're integrating is written in C#, bepuphysics2, it's really cool how deep into the engine code you can dig, it's like 90% C#)

MonoGame https://docs.monogame.net/articles/getting_started/3_underst...

Flax https://docs.flaxengine.com/manual/scripting/index.html

And a few others (like NeoAxis, that one is really niche, but I think their voxel LOD idea was cool, even if it didn't run well for me). I wonder why Java doesn't see the same level of attention, since the runtime itself is pretty good and the modern versions of the language are reasonably nice, yet for Java it seems that there's only LWJGL and jMonkeyEngine.

That said, .NET is really good even when it comes to webdev, I have to admit that ASP.NET feels way more coherent than working with the likes of Spring Boot in Java sometimes does, I guess I'd say that it's more of a focused and less fragmented experience.


Most people I know working with C# in a professional capacity use the commercial version of Visual Studio, though there's a decent free Community version. Back in the day, VS Code would limit you pretty heavily particularly if you were using WinForms or WPF.

I actually used to really like Visual Studio, but around Visual Studio 2015 it felt like very subsequent release was more bloated and slower, and I eventually jumped ship for Rider. But by that point, I was really only using C# for Unity projects, and the rest of the time I was using JS/TS and Python.


Tried it for a while, paid for the full bundle for a year to try a few of the products. Just could not see what the fuss was about, Visual Studio (the full version) does everything I need it to.

We've transferred almost every core developer to Rider. Visual Studio runs crappy due to do much telemetry and anti-malware on the system. Rider still runs somewhat bearable.

I’ve been using WebStorm and RubyMine for 5+ years and I love them. I’m definitely going to try to make Rider work before falling back to Visual Studio.

.NET is a very productive stack, if you're coming from a JS background you'll find all of the batteries included that'd usually require a dozen or so NPM packages.

If you're building a Blazor app, I'd suggest sticking with its SSR project type. WASM is fine but SSR is far more productive. The hybrid mode is a recent addition, but it has too many rough edges and I'd avoid it.


I think we’ll be priced into using WASM mode for at least one of our products, because we have VoIP functionality built-in. The call needs to stay live and controllable as the user navigates the application. I think that’s the main reason they chose Angular rather than .NET MVC.

Anders Hejlsberg was chief architect of .net after creating both Turbo Pascal and Delphi.

At work I’ve recently moved from a Node/TS monolith to “Python+TS react in a sea of .NET microservices I debug and contribute to”

It’s been the second time in my career I’ve been surprised by not hating C# (the first was goofing off with Unity in 2018). The language itself has a lot of niceties; for example a method to turn the variable foo into the string “foo”. The Neovim LSP set itself up just by installing the dotnet executable. And the syntax for creating complex workflows were pretty ergonomic once an experienced .NET dev walked me through what I was even looking at. I still prefer FastAPI + well-typed Python as the backend framework of my dreams… but I’d work in .NET again.

Blazor hasn’t sold me yet, but seems like a fine choice. It fits in the same class of tools to me as Django Templates, HTMX, or JS handlebar rendering. There’s a class and size of apps for which that’s perfect, and there’s some value in a fullstack language keeping your stack monolingual. But IMO the framework should stand on its own against frontend frameworks like React, Vue, or Svelte… with the simplicity of monolingualism added as a cherry on top. Otherwise you’re optimizing towards the number of languages your devs need to learn over which frontend framework would be the best fit for your app. And between the DX and expansiveness of the JS ecosystem, it’s been hard to imagine going back once you’ve spent a few years eating the shamefully-complicated-constantly-shifting-and-reforming elephant that is learning TS React and friends


> eating the shamefully-complicated-constantly-shifting-and-reforming elephant that is learning TS React and friends

Can I say again how nice it was to use EmberJS from its release candidate days all the way through my seven year tenure at that job? Batteries included, Promises way back in 2013, and way more stable than anything else.


I started C# and .NET a few years ago and I still enjoy it. The cross platform initiative has been such a relief. OK it's Microsoft and please set the environment variable

  export DOTNET_CLI_TELEMETRY_OPTOUT=1
, but believe it or not, I'm on Linux and I use C# every day.

The most significant advantage over other languages I noticed is that it is a very versatile language. You can do basically anything with it and it usually works cross platform out of the box. CLI, GUI, WebService, Mobile Apps and even WASM is also included. With the new PublishNativeAot option you can even build native apps without depending on the runtime. It's not Rust or go in size or performance, but it often is close enough. And boy there are great libraries for nearly everything.

  Command Line App? - Spectre.Console 
  WebService or Api?  - FastEndpoints
  Cross platform GUI? - AvaloniaUI
  Image handling? ImageSharp
  ProcessHandling? CliWrap
  Unit-Testing? xUnit, FluentAssertions and NSubstitue or the new TUnit
  Hate Exceptions? Rust-Style error handling is possible [1]
The only thing I'm really missing is a well maintained cross platform audio library with a permissive license. That's why I'm learning P/Invoke now to create a C# wrapper around miniaudio[2]. So I'm still learning C# after all these years and haven't hit the wall.

Good luck!

1: https://github.com/gnaeus/OperationResult

2: https://miniaud.io/


I've been learning C# and dotnet core for the past couple of months and it's been a great experience.

I love C# and wish I could write all my web stuff with it. Unfortunately, Blazor WASM is so far behind pretty much all JS front end frameworks in terms of performance, bloat, and memory usage.

https://krausest.github.io/js-framework-benchmark/current.ht...


Ugh, I like enterprise apps but I can't get behind using Blazor. It reminds me too much of JSF. I hated that monstrosity.

I just think it's good practice to decouple the frontend. If you're a tiny team and you already know how to use something like Blazor, maybe it makes sense. But that's the only place I see it being valid.


If it's Blazor WASM the server side would a conventional rest API. If it's server rendered Blazor it would be a hybrid in a somewhat similar style to Server Rendered Components found on other frameworks.

I’ve never been able to overcome general “hostility” toward Microsoft to really learn it. I like C# as a language, and think it does a better job of being “a better C++” than Java, but can’t get past it.

Since it was opensourced I don't really care anymore. Not like Oracle or Google are great companies either.

Same. I spent way too many years angry at Microsoft for the web stagnation during the IE years. Now that .NET and C# are open source and Edge auto-updates, I think I can swallow it.

Always wanted to try our Blazor, but there are weird barriers (as a layman) to understanding the client-side lifecycle. Maybe just opaque abstractions that don't seem necessary at first glance.

In my limited experience, Blazor significantly simplifies front-end development, particularly in larger and more intricate projects. This is partly due to the maturity of .NET tooling and ecosystem, which makes it easy to manage projects at different scales of complexity.

"It works on my machine" is not simplifying front-end development. Please just stick with React, Hotwire, HTMX, literally anything else.

I’m not sure what you’re referring to. Blazor, along with Razor pages, was developed to simplify front-end development in dotnet. It’s essentially the React of the C# world, enabling the embedding of both C# and HTML into Razor pages, both on the client and server sides.

Its most impressive feature, however, is LiveView, which is akin to server-side rendering. It’s more sophisticated than that. It does a diff on the state changes and only the diffed DOM node states are transmitted over the network, resulting in lightning-fast performance. In other words, if a value changes from <p>5</p> to <p>10</p>, only the 10 gets sent from the server (along with sufficient information to locate the correct location of the replacement).


I’ve recently learnt blazor as a non-dotnet person. I definitely think it comes across as complex at first glance. The learning is pretty front loaded (especially with efcore too) but it’s not too bad once you dive in.

In particular, I am a big fan of the Maui blazor hybrid stuff. Worth a look if you fancy making a desktop app.


Don't. Unless you're a client-side web tech expert, Blazor will break under production load.

Things will work fine in QA, but it's not a robust framework. So when things break, they break hard, leaving your end-users in the ditch.

Something like Phoenix LiveView is a much better option.


Is it using something like websockets to stream data?

Yes, it can, and by default be through SignalR. The problem is both SignalR has its own limitations as well as web-sockets in general.

I have never seen a SignalR (and recently Blazor) implementation without significant and characteristic issues the moment you actually star to observe end-user errors/issue/anatomies. Either through automated reporting or user feedback. And this is because SignalR is built on .NET and .NET has many different threading issues when you need those thread to be stable and robust (not talking about transit B2B apps)

It always happens. Without exception.

You do not have to take my word for it, look at the active, open, and historical issues here: https://github.com/SignalR/SignalR/issues

The issues are plainly fundamental.

It's absurd. And it only survives because it's Microsoft.


That version of SignalR you are referring to is from .NET framework and really bolted on. That version of the framework had async/await in ASP.NET also bolted on, and the threads itself use a synchronisation context and locks.

This is mostly no longer the case in modern .NET. SignalR is a core part of the framework and completely rewritten.

In our case, Blazor Server has been very stable for us, also for multiple users.


Can someone who's done it let me know the appeal of the Angular to Blazor migration? it seems a such a common pathway.

I've used; React, Blazor and svelte professionally but never Angular. Sure, the node ecosystem has warts but I've found both the DX and end product of Blazor to be so wildly behind the JS ecosystem that I can't see myself choosing it again. Is there something uniquely bad about Angular or something?


I think Visual Studio used to have a preset to generate a web application with a C# API and Angular front-end (perhaps because of its strong TypeScript culture?). In that case, there are a lot C# / .NET shops out there using Angular. For those teams it simplifies a lot to use one language.

A bit of a tangent but .NET CLI is excellent and I find it more productive at project management/scaffolding/building/etc. It has quite a few useful templates for 'dotnet new' command. Though I found the Angular one a bit outdated back then, but perhaps this changed.

I am learning C# as well after decade of powershell, bash and (a little) python to fill security tool gaps. It feels heavy because I have to actually worry about error handling, speed and the quirks of compiled languages. Ive also had a great time learning with ChatGPT - i can ask it to explain and compare a C#-ism to a similar powershell or python pattern. Hugely helpful.

> It feels heavy because I have to actually worry about error handling, speed and the quirks of compiled languages.

This is a bit unfortunate. It doesn't have to be this way - i.e. you don't need to write "extra" syntax many adopt out of blind adherence to old, outdated practices (i.e. 'private' or'internal class' usually makes no sense to use - these are default visibility levels anyway, you're repeating yourself, or reimplementing Java-style heavy visitor patterns).

If you see there's a shorter path to get from point A to point B it's almost always a good idea to take it. Even if something seems less than optimal, it will still run an order of magnitude faster than any interpreted language. There are obvious language-agnostic gotchas you may want to avoid but otherwise there is no reason to not use e.g. LINQ in regular application logic.


The main downside of C# when compared to Java is its weaker exception handling, see here: https://mckoder.medium.com/the-achilles-heel-of-c-why-its-ex...

Some people will say the opposite.

https://gen5.info/q/2008/07/31/stop-catching-exceptions/

Checked exceptions don't cause people to write good error handling code, they just cause a crisis for no good reason when you are writing code that people will answer with some lame answer like

  try {
    action();
  } catch(ActionException x) {}
or

  try {
    action();
  } catch(ActionException x) {
    throw new RuntimeException(x);
  }
or

  try {
    action();
  } catch(ActionException x) {
    throw new SomeExceptionThatWillCauseACrisisLater(x);
  }
when you really should be doing something like

  try {
    action();
  } finally {
    makeItRight();
  }
and finally deal with the exception at the end of the "work unit", see

https://gen5.info/q/2008/08/27/what-do-you-do-when-youve-cau...

Look at the JDK 8 streams library of an example of a library that (a) is awkward as hell because you can't

   stream.map(this::someMethodThatMightThrowAndException)
but (b) still handles errors improperly. Some versions of Lisp have a much better approach described here

https://gigamonkeys.com/book/beyond-exception-handling-condi...

in most languages what you can do is build a framework that controls execution in such a way that (in the context of a stream of work units) it can "separate the code that actually recovers from an error from the code that decides how to recover" as that article says as best you can.


People only handle checked exceptions that way because Java the language hasn’t given them the tools to deal with them properly (Swift does a good job at this) and historically people would check exceptions from their downstream function calls that they couldn’t handle rather than unchecking them.

Checked errors are good and are so important to have program correctness, but you need a way to easily escape them when necessary. Rust and Swift have both have realized this. Rust provides ? and Swift has try!, Java needs the same.

> Look at the JDK 8 streams library of an example of a library that (a) is awkward as hell because you can't > stream.map(this::someMethodThatMightThrowAndException)

This is again Java the language not providing the necessary tools. Checked exceptions can work across high order functions: https://docs.scala-lang.org/scala3/reference/experimental/ca...


My impression (based on two projects) is that Scala is snake oil.

(1) We were trying to parallelize an easily parallelizable task in Scala. Except it would only use a fraction of the CPUs available and didn't always give the right answer. In three days I was no closer to getting the Scala solution using all cores, I was able to do it in three minutes with Java Executor.

(2) I saw a somewhat larger code base that implemented a data processing pipeline. I was told by the eng manager that (a) we do code reviews and (b) we use monads for error handling. I guess we did, except it was the monad equivalent of

   try { something() } catch(SomeException x) {}
most of the time which, once more, fits pattern of people writing exception handling code to silence the compiler.

In principle something like monads could let you implement more complex error handling strategies (like Lisp) but so long as "a monad is a like a burrito and a computation is like a graph", monads will be underpowered. Note you can use polymorphism for handling errors in Java too, for instance

   interface FunctionThatThrows<In,Out,X> {
      Out applies(In arg) throws X;
   }
JDK8 streams could have done better with the tools it had.

A bad programmer can write bad code no matter the language. Java lets you write good code if you're a good programmer. C# on the other hand makes you write bad code even if you're a good programmer. Why? Because you have no idea what exceptions are possible, and whether the handlers you have are even needed anymore.

I participate in a couple of Java communities and checked exceptions are considered a huge anti-pattern in both. Please don't mistake preference for objective reality.

And yet using checked Results seems to be the pattern that most programming communities are heralding. Java has just made checked errors awkward to deal with.

This is what happens when a wrong narrative takes root and you don't nip it in the bud. Many programming communities have embraced the wrong narrative, and if you approach those communities and challenge them, all they can respond with is, "everyone else says so, so you must be wrong".

> huge anti-pattern in both

That's not a valid argument. Lots of people believed the earth is flat but we now know that's not true. Did you read the article? Explain why it is wrong.


Sibling comments did a better job than I could've done at explaining why the checked exceptions and business logic exceptions are bad.

> why checked exceptions and business logic exceptions are bad

It didn't, and they aren't bad.


"For example, if you indiscriminately catch Exception (instead of CreditCardExpiredException, SuspiciousActivityException, CardTypeNotSupportedException and so on) in order to recover from a credit card transaction failure, you may inadvertently catch SQLException as well, but that is a different type of error"

All of the above examples of custom exceptions are great examples of things that should never be handled by exceptions. If the author was using a library that really threw exceptions like this, then it would probably be best to just find a new library which isn't arbitrarily throwing exceptions to create control flow logic.


There is nothing wrong, I think, with having application-oriented exceptions, but you are going to either catch them individually or make them derive from some root like CreditCardException.

Real life does not respect encapsulation, that is, it is not really your application's business to know that such a thing as a SQLException exists, other than how to log it, if SQL is hidden beneath a DAO layer. I mean, just because the beautiful architecture of your application doesn't have things like backhoes cutting through fibers in it, doesn't mean those things can't happen to you. There are a lot of things that will happen to your application that it can't understand and the best it can do is: (i) protect its own integrity (use finally) and (ii) abort, retry or ignore and (iii) hopefully have the wisdom to choose the right one of those.


> should never be handled by exceptions

So you prefer to check for errors after every function call? That's what you had in C language, and the problem is that the logic gets buried inside all the error checking, and that makes code hard to read (and write).


The examples in that article (CreditCardExpiredException, SuspiciousActivityException, CardTypeNotSupportedException) show why no other languages follow Java's concept of checked exceptions. Those are examples of business logic or control flow being done by exceptions, which has been, in my experience, considered an anti-pattern for a very long time.

Other languages use union types, enums, or other type system constructs to represent operations which may have multiple possible outcomes. You're correct that these languages don't support business logic exceptions, but that also isn't an acceptable practice outside of Java.


> acceptable practice outside of Java.

What is the acceptable practice outside of Java?

Do you prefer to check for errors after every function call? That's what you had in C language, and the problem is that the logic gets buried inside all the error checking, and that makes code hard to read (and write).


The general answer is "represent it in the type system". That's typically via discriminated unions like Rust's Result, or something which emulates it (such as OneOf or Dunet for C#, although it will be a native part of the language soon).

Pattern matching, nullable types, and the Try* method conventions are also ways of representing the potential for failure explicitly.


Checked exceptions are represented in the type system. There is fundamentally no difference between these two:

    A b() throws C
    fn b() -> Result<A, C>

    A a = try {
        b()
    } catch (C c) {
        new A()
    }

    val a = b() match {
        Ok(a) => a
        Failure(c) => A()
    }

I’m really passionate about exceptions and checked exceptions in general. Java really has done a disservice by not making it easy to uncheck checked exceptions. Much like rust I hope they offer some sort of ? operator in the future. This really is the main gripe with checked exceptions.

> uncheck checked exceptions.

What do you mean? if you add "throws Exception" to every method then you can defeat checked exceptions but please don't do this in production code


Right. What I mean is that when you can't handle an exception and you should rightfully become unchecked it's extremely verbose. For example if I'm reading a config file and I should rightfully panic and crash my program if that file doesn't exist:

    Config config;
    try {
       config = readConfig("/some/path/to/a/file/that/must/exist.txt")
    } catch (IOException ex) {
       throw new RuntimeException(ex);
    }
    
    doSomeStuffWith(config);

Realistically situations like this should be:

    Config config = readConfig!!!("/some/path.txt"); // !!! being some operator that says "shut up compiler crash if this happens"
    doStuffWith(config);

People do not like checked exceptions because they either need to check and colour the whole stack or write way too much code to panic.

It is not very common to run in to an error that should crash the application. If you have to write a bit of code to do that, in my opinion that's fine.

I'm just going to say that you're opinion is wrong. You're opinion is the reason that the majority of errors in C#, Java, JavaScript, Dart, D, and all the other languages that use exceptions have decided that all errors should crash the application and that you won't know about what can error.

Programmers are LAZY. Making them do verbose things when they can't possibly handle it are going to cause them to reject the language feature.


In earlier versions of C# (including .NET 3.5), Access Violations did not inherit from Exception, and could not be caught using "catch (Exception ex) { }" syntax. You had to use "catch { }" syntax instead.

You still cannot catch AccessViolationException, StackOverflowException or ExecutionEngineException.

This is by design because each indicates catastrophic unrecoverable failure (you could have a couple of hand-wavy arguments about AVE dereferencing wrong address but, really, that indicates critical error in implementation or memory corruption still).

You should never see any of these regardless. Aside from abusing stackalloc by passing a large number I guess.


Heroku (I work there) recently announced support for .NET on the platform https://blog.heroku.com/dotnet-support-on-heroku

That’s awesome! Heroku was incredibly important for Rails beginners for years. I wonder if this will move the needle on .NET adoption for beginners.

Does C# still require you to write everything with classes? Would love to try it if there's option to write functional code in it.

They introduced the concept of top-level scripts a little while back - though I don't know whether its just syntactic sugar such that when it compiles down to IL everything just gets wrapped in a main class.

https://learn.microsoft.com/en-us/dotnet/csharp/fundamentals...


It's just syntactic sugar. That said, a static class with static methods is basically a namespace.

You still need to have a class to put your functions in, but they can act only as a namespace, like `Math.DivRem()`.

C# has been receiving many functional features over the years including pattern matching, composable data processing (Linq), tuples, and immutable data types. I'm looking forward to a good implementation of discriminated unions now.


"classitis" is much diminished

You know about F# right? https://dotnet.microsoft.com/en-us/languages/fsharp

https://learn.microsoft.com/en-us/dotnet/fsharp/whats-new/fs...

Dotnet has compilers for C#, VB and F# and they can all share the same frameworks and libraries. C# is the most popular.


I’ve heard it’s multiparadigm now, but I can’t yet substantiate that. Even if it is, I don’t know if it’s JavaScript-level (so functional they rewrote SICP in JS), Ruby-level (just a sprinkle of lambdas and pattern matching), or somewhere in between.

I’ll be sure to post an article once I find out. :-)


Great choice, I miss .NET every day.

I gave up on C# and .Net after realising Microsoft is still the same old Microsoft I loathed back in the 90s and 00s when IE ruled the world. The emperor's new clothing is cross-platform .Net. The reality is that whilst .Net may well run on Linux and OS X the culture surrounding .Net hasn't changed. Case in point, I recently assessed .Net's leading CMS - Umbraco - for a project. Yes, it runs on Linux and OS X but can you use standard open source databases such as MySQL and PostgreSQL with it? Sorry, not supported. You have to cough up for a MS SQL Server licence to get your open source CMS up and running. As if to rub salt in the wound Umbraco does support SQLite but wtf!? M$ culture will never change. Their lip service to Linux and open source only happened because their hand was forced after decades of pursuing the likes of Red Hat and SuSe with bogus patent lawsuits and secretly funding the notorious SCO in their Unix patent litigation.

> bogus patent lawsuits and secretly funding the notorious SCO in their Unix patent litigation

:D

I'm curious, could you link the lawsuits?



C#/.NET is my go to always. But, I have a really hard time loving Blazor, and most of the other non-essentials that are piled on top of HttpContext.

My current preference is to lean on string interpolation [0] to do most of the heavy lifting when rendering web documents on the server. You can nest interpolations and use LINQ + collection aggregation (string.Join()) to trivially build things like tables and other complex documents. Combine interpolation with verbatim [1], and you basically get a poor man's PHP document system nested within your C# source files. I've done the whole razor template thing and I am completely over that. Managing the dependencies and weird initialization routines for these rendering engines is not worth it when the language has a built-in feature that accomplishes virtually the same thing with absolutely zero BS.

Writing raw HTML to the client is a simple one-liner [2]. You can quickly build your own bespoke web frameworks, leveraging the power of features like reflection to make your life 100x easier. You can sidestep virtually all of the AspNetCore namespaces and the attribute-driven nonsense that is hallmark of most .NET/MVC-style web apps. If you keep it light and avoid the boilerplate, you can do a lot of damage within 100 lines of code these days.

[0] https://learn.microsoft.com/en-us/dotnet/csharp/language-ref...

[1] https://learn.microsoft.com/en-us/dotnet/csharp/language-ref...

[2] https://learn.microsoft.com/en-us/dotnet/api/microsoft.aspne...


Modern C# is nice, Blazor not so much.

I've been able to pretty much drop Javascript for business oriented apps due to Blazor (both server rendered and WASM)

It's 2025 and there are still companies doing "business corporate crap" in .NET...

"Patterns of Enterprise Application Architecture", even just the title of this book reminds me of the good old time when you would hire 30 java consultants to do online forms for business with java beens, j2e, .JSP and co, delivering the worst and most buggy user experience facing a lean website like "facebook" created by 2 students doing simple html/php apps.




Join us for AI Startup School this June 16-17 in San Francisco!

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

Search: