Hacker News new | ask | show | jobs
by pdmccormick 654 days ago
I'm genuinely curious, for someone who develops web application backends and larger distributed systems & infrastructure, predominantly using Go and Python, exclusively targeting Linux, is there anything in the .NET ecosystem that anyone would recommend I take a look at? Many thanks.
8 comments

.NET Core is my favorite way to quickly implement an app to run on a Raspberry Pi. Just basically copy & paste into a folder, chmod the executable and off you go.

I have a number of these devices running in the house doing various things.

You may want to look at F#. Because it's .NET you have a large list of libraries you can use
For a beginner, high school but zero experience in programming, which one easier to learn, C# or F#?
Definitely C#. You’ll find tons more resources. F# is fantastic, but it’s not a good *first* programming language.

A lot of what you’ll learn when you first learn programming is going to be applicable in any language though. Once you’re comfortable with C#, and can understand the difference between imperative, object-oriented, and functional programming, you’ll be in a good place to check out F# (or any other language, really).

Good luck with your learning!

It doesn't matter, if you want to "actually" use .Net you have to at least be able to read C#. And I guess some files still - as it was 3 years ago - need to be C#, for example in mobile apps.
Its an interesting question. I've found personally people with previous imperative/functional language (e.g. JS/Go/etc) have picked up F# quicker, and people with OO knowledge (C++, Java, etc) have picked up C# quicker. There's a lot of implied/conventional knowledge with OO that many C#'s dev forget they have (i.e. its all sunk cost to them). If you just want to cut and paste code however C# has more Microsoft provided doco so there's that.
C#
Thanks
Honestly this is such an interesting question. Conventional wisdom would definitely say C#, but I’ve always wondered if that’s because imperative programming is easier than functional for a beginner, or because basically everyone starts with imperative. I’d be curious to see what would happen if someone started functional first.

All that said, probably C#.

Modern .net on Linux is lovely, you can initialize a project, pull in the S3 client and write a 1-3 line C# program that AOT compiles to a single binary with none of the perf issues or GIL hand-wringing that plagues life in Python.

Given modern Python means type annotations everywhere, the convenience edge between it and modern C# (which dispenses with much of the javaesque boilerplate) is surprisingly thin, and the capabilities of the .net runtime far superior in many ways, making it quite an appealing alternative especially for perf sensitive stuff.

Do your civic duty and disable telemetry everywhere you go. :)

export DOTNET_CLI_TELEMETRY_OPTOUT=1

I don't understand. How does that help cross platform?

All I see is a manager saying, "the data shows no one uses it"

> for someone who develops web application backends and larger distributed systems

Blazor: It's Microsoft's way of doing in-browser C#. It can do quick-and-dirty server-side HTML, and professional-grade, in-browser WASM.

Why is this useful "for someone who develops web application backends"?

The nice thing about server-side Blazor is that you can make a management console, or otherwise port ops scripts, into a self-service page. Because you can choose to render on the server, you don't have to write an API, serialize your response, ect. You can do a SQL-ish query (with LINQ and Entity Framework) in the middle of HTML.

(Granted, for production-grade pages Blazor can run in the browser as WASM and use industrial-strength APIs.)

As someone in same spot I'll say that .NET looks more than interesting after so many years using 6-8 languages daily. And I'm more "make it works, not shine" type.

Why .NET > Go in my opinion? - performance-wise the gap is not big and probably even .NET can be quicker - development time can be reduced, tooling is great for .NET and even funny-not-funny error handling is cleaner - still much easier to find people in .NET than Go where I live and work

Now it's time to verify those assumptions - I'm going to implement next real project in .NET and see how it went. Hobby or "trials" in .NET resulted in fun and speed, but it often happens on first date :)

It’s great for web application backends. Switched to it after 10+ years of Python. Couldn’t be happier.

Binaries will be huge tho compared to Go. I’ve a few CLIs that I that I my customers need to use, I’m planning to rewrite them in Go for this reason.

Is AOT compiling your binaries [1] an option for you? The starting size of AOT compiled C# can beat Go in size [2] and from there it really depends on what you do and how you do it. Some simple ASP.NET server with https and routing can comfortably fit under 10 MB and there are compilation options that can help optimize further [3].

[1] https://learn.microsoft.com/dotnet/core/deploying/native-aot... [2] https://github.com/MichalStrehovsky/sizegame?tab=readme-ov-f... [3] https://learn.microsoft.com/dotnet/core/deploying/native-aot...

I looked into it but some libraries I've been using wasn't compatible with AOT. I'll check it again when I have more time. Thanks.
The feature I always suggest as uniquely C# flavoured is LINQ.

https://learn.microsoft.com/en-us/dotnet/csharp/linq/get-sta...

Although the SQL-like form isn't always favoured, and quite a lot of the time I use the plain OO one.

Oh yes, extension methods: do you want object X to support method Y, but can't change object X? Well, provided you don't need access to anything private, you can just add a method and do X.Y()

I last touched LINQ in college in 2016 - isn't it basically an ORM for C#? Not super unique but I assume very relevant to use when working with C#.
EF ("entity framework") is the ORM. LINQ lets you write queries against any collection, such as a Dictionary or a List. So I write lots of "listOfFoo.Select(x => x.Name).ToArray()" style code with it, which compiles down efficiently.
These days "pipeline oriented programming" (which is what LINQ is) is seeping into many modern programming languages like Rust, although array programming languages are still (unreadable) kings at it.
LINQ is just the way .NET calls iterator expressions that are a staple in any language that claims to be good and modern.

There are two main interfaces in .NET that have different behavior:

IEnumerable<T> which a sequence monad, much like Seq types in FP languages or IntoIterator and Iter (IEnumerator<T>) in Rust. This is what you use with whenever you 'var odd = nums.Where(n => n % 2 is 0);`.

IQueryable<T> which is what EF Core uses for SQL query compilations looks the same as the first one, and has the same methods, but is based on something called "Expression Trees" that allow runtime introspection, modification and compilation of the AST of the expressions passed to Select, Where, etc. This existed in .NET for ages and really was ahead of the time when it was introduced. You can write a handler for such expression trees to use LINQ as sorts of DSL for an arbitrary back-end, which is how EF and now EF Core work. You can also compile expression trees back to IL which is what historically some of the libraries that offer fast reflection relied on. Of course this needs JIT capabilities and runtime reflection, which makes it AOT-incompatible - calling .Compile() on such query in a JIT-less application will be a no-op and it will be executed in an interpreter mode. It is also difficult for the linker to see the exact types that are reflected on which means you have to annotate the types you want to keep and AOT compile code for. Which is why this mechanism is largely replaced by source-generation instead, closer to how it happens in C++, Rust, etc. An example of this is Dapper AOT.

Just realized it should have been

    var even = nums.Where(n => n % 2 is 0);
I'm so sorry
C# is IMHO still better language than Go.

Refactoring tooling is unmatched.

If with C# you can create a fully static binary which runs on an empty scratch docker, I will properly consider your opinion. Until then: Go à gogo!
You mean `dotnet publish -r linux-x64 --self-contained` ? This will embed the runtime in the executable. You can also do trimming so it removes anything that's not used. Also, there's AOT but it's got a ways to go.
Sibling comments talk about self-contained. I'll just be pedantic and say that this has nothing to do with the language itself.
You can. It'll probably be bigger than the Go binary but you can.
The "fully static binary" only works because Go ships cryptography and most other usually host-provided features that other languages rely on host's libc instead, at the cost of performance, limited feature support and requirement to recompile everything in order to ship (inevitable) security fixes, which did happen in the past.

.NET native compilation toolchain supports this mode but it's not a default for a reason (causes binary size bloat too, musl is rather small, but ICU is very much not).

(just to be accurate - all C# and runtime code becomes a single static executable, but cross-compilation is possible between CPU architectures within OS only, with additional options enabled by 'PublishAotCross' nuget package that switches to Zig toolchain's linker so you can AOT compile for Linux targets under Windows, for "self-contained trimmed JIT executables" you can target any OS/ISA regardless of what you use)

Anyway:

    dotnet new console --aot #or 'grpc --aot', or 'webapiaot'
    dotnet publish -o .
Notes: gRPC tooling is a bit heavy, webapiaot template could be improved in my opinion

As of today, ILC has become better at binary size baseline and scalability due to more advanced trimming (tree-shaking) analysis, metadata compression and pointer-rich binary section dehydration (you don't need to pay for embedding full-sized pointers if you can hydrate them at startup from small offsets). You can additionally verify this by referencing more dependencies, observing binary size change and then maybe looking at disassembly with Ghidra.

Also better capability for true static linking - you can make .NET NativeAOT toolchain produce static libraries with C exports that you link into C/C++/Rust compilations, or you can link static libraries produced by the latter in NAOT-compiled executables[0][1]. It is a niche and advanced scenario that implies understanding of native linkers but it is something you can do if you need to.

Binaries compiled in such a way will have its interop become plain direct calls into another section in it (like in C). There will be a helper call or a flag check to cooperate with GC but it's practically free. Costs about 0.5-2ns.

[0]: https://learn.microsoft.com/en-us/dotnet/core/deploying/nati...

[1]: https://learn.microsoft.com/en-us/dotnet/core/deploying/nati...