DEV Community

Eesaa Philips
Eesaa Philips

Posted on

From C# to golang: less is more.

I had been writing .NET backend systems for more than 4 years. I've used it at work, side projects, and college projects.

Recently, I decided to use go to build the backend for a mobile app. I value simplicity, and therefore enjoyed go the moment I stepped through go by example

Go prioritizes simplicity and intent. It is imperative and eliminates many complexities that haunt other languages by choosing a small subset of features. If you're coming from C# or java, you may feel confined by this minimal language at first.
But you will quickly realize that go's strength comes from its simplicity.

Things I like

No need for external dependencies

Everything works out of the box. You can build an entire backend with middlewares, services, repositories, database connectors, etc. without ever looking beyond the standard libraries go provides.
In contrast, building a similar system in C# requires external dependencies.

Go is not opinionated

There is no right way of doing things in go. For example, if you want dependency injection, you set it up yourself and pass your services as parameters. Go does not do any DI magic under the hood which makes you mindful of how you create and pass your objects.
Go offers more freedom than C# which can lead a new developer to stumble until they find what works best for them; but I see this as an advantage. It forces you to make deliberate choices about the structure and flow of your code rather than blindly conforming to a predefined structure others have placed for you.
.NET's minimal APIs are the closest you can get to Go in a C# backend. It's a step in the right direction.

Built in testing

When testing in C#, I would usually use an external library like xUnit and another for mocking. In go, testing is built in. You don't need to rely on any 3rd parties to test. You can benchmark, setup before tests, and teardown after tests using the built in testing library.

Less Boilerplate

It takes less code to get a server up and running than C# .NET or java spring. This is a hello world http server in Go:

package main

import (
    "fmt"
    "net/http"
)

func main() {
    http.HandleFunc("/", HelloServer)
    http.ListenAndServe(":8080", nil)
}

func HelloServer(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintf(w, "Hello, %s!", r.URL.Path[1:])
}
Enter fullscreen mode Exit fullscreen mode

I know a minimal C# API is just as small if not smaller, but most backend C# systems use controllers. A typical C# codebase will likely be much larger and contain more boilerplate compared to go.

Extremely Easy to Learn

You can learn go in a few days and build something with it in no time. The learning curve is far from steep. You just hop in and next thing you know, you're spinning up services in no time. This is also great for hiring: it's easy to get a junior dev on the team and they will be contributing soon after.

Concurrency

Concurrency in C# is complex, especially for new developers. There are many ways to achieve concurrency but equally many caveats and catch22s. You can use Parallel.invoke, Parallel.forEach, Task.WhenAll, Task.WaitAll, and more. With these abstractions, you gain granular control over the task: you can control if it's blocking, configure cancellation, etc. But you also risk running into countless complications. Be careful if you don't want the bloodcurdling deadlock monster entrapping you. example

In go, concurrency is straightforward and untroublesome. you simply use the keyword go to run a function asynchronously. For more control, you can use channels and waitgroups which go provides to allow for fine grained control. Go's goroutines are similar to C# tasks where multiple goroutines map to a small number of OS threads. One of the differences is goroutines contain normal go code while C# tasks need to be written (or potentially re-written) to be asynchronous.

Things I Dislike

Lack of Mature Generics

Generics were recently added to golang but they are not mature. You cannot use generics without instantiation which limits your options. Functionalities that are easily achievable with generics in other languages may require workarounds in go.
Another pertinent issue is the lack of support for union types on struct fields. See this issue.

Lack of Built-in Higher-order functions

Go does not have higher-order functions out of the box. If you're used to using LINQ in C# or streams in Java - which use lazy iterators to filter and process data immutably - you might be disappointed. Technically, you could write functions like these in go using closures. In most cases, the performance and complexity overhead of these custom iterators outweigh the benefits. This is by design. Go is meant to be simple. But I still find myself missing the niceties of LINQ from time to time.

Closing Thoughts

If you noticed, I did not compare performance because it doesn't really matter in this context. You can build very capable and performant systems with C#, go, java, rust, etc. Your performance bottleneck will almost never be one of these languages, but rather your design choices.
I also refrained from complaining about Go's error handling because, although it's not as good as Rust's or C#'s, it's still fine and forces the developer be mindful about handling the errors.

If you haven't already, give golang a try.

None of this really matters; just build something

You can sit here all day arguing about syntax differences and language superiority, but it really doesn't matter. The best programming language is the one you're most productive with. So just go out there and build something. Build it with go, java, or even assembly. Just avoid idle hands.

Top comments (3)

Collapse
 
duongphuhiep profile image
DUONG Phu-Hiep
  • About concurrency: Most app rarely need any "Parallel.xxx" or "go xxx". Most of time we only need Async for I/O operation. in Go it is often by default, you don't need to create a go routine for I/O operation (in most case). In .net, it is trivial to achieve async I/O with async, await. Real concurrency is always hard to do correctly even with Go (checkout the Go's conc package)

  • About boilerplates codes: the Go language has simpler syntax, less sugar/magic codes, you usually need much of boilerplates Golang's codes to achieve the same thing in C#. In other words, Go's code are usually more verbose than C# codes. The most Go's "boilerplates" is obviously the indispensable "if err != nil" everywhere.

  • About eco-system: The Golang's eco is still young. You often don't have much choice for the things you need, if it was something uncommon or not very popular, it might become real blocker! On the other hand you basically can find anything in the .Net eco-system. You are likely concentrate on developing your application rather than becoming another library author because of some missing (or unsatisfied) libs.

Collapse
 
addisonrogers profile image
Addison

Well written article. Only critique is that I have found I need to use more external dependencies in go rather than dotnet.

Collapse
 
emreaka profile image
Emre AKA

DI is simple. Why you said it is magic?