Hi. I know C# in amateur fashion and Go professionally. I think a lot of people here will give you a very positive review of Go. Since they've got that covered, let me tell you what you're giving up from C# or F# and why I won't use Go. Maybe you can form a balanced conclusion from the aggregates, because I find Go to be very polarizing.

Concurrency

Async: Go uses channels for all concurrency. Period. This mechanism, is sort of like half of the Erlang Actor philosophy, but even more lightweight. Channels and goroutines are constantly coming into and out of existence and you feel no real shame doing it. Even simple tasks often need them becuase Go's I/O libraries tend to be async-first.

Compared to C# and F#'s async, I think you will find this to be very different, but not particularly better in terms of performance. F# offers a very similar abstration with very similar performance characteristics and C# async methods are using a very similar mechanism under the covers to provide closure over computation chains in what amounts to a kind of effectful state monad. Don't sell your home team short on this front; MS's work there is cutting edge.

Error Handling

In daily programming, Go's weakest story is error handling. While many people rightly cricisize try-catch error handling as a primitive and error prone mechanism, the Go solution is to say, "We all hate C but actually C erorr handling was fine so long as you have multiple return values at the syntactic level." So you often return a success value (which is nullable) and an error value (which is nullable) and then ask the caller to check on the null.

This is basically error code checking. People will say it isn't, but really it is. It really is. And unlike some other langauges Go provides no facilities for "chaining" these operations. So you end up writing if err != nil { ... } over and over.

In the case of chained I/O operations, this is really tiresome. It also often leads to code repeating or some somewhat convoluted dispatch logic.

Go Error values also suffer from Go's other issue, it doesn't have an extensible type system. Instead it has "interfaces". In practice, what this means is that it's very difficult to expose new error types or give your clients good ways to dispatch on them. While this means error handling code is lightweight, it also often means it has to do silly things like regex and error message string to find out what a specific failure was.

Some people value that approach. If you're writing an executable it's actually good, becuase it's probably better to fail fast in a recognizable way. But if you're writing a library and offering OTHER people that facility you can't support them well (and you will not be well supported by Go libraries).

Build Tooling

Go's general toolchain is solid and its compiler is wicked fast. But its build story is still really, really bad. Go originally had this mountain of filesystem around every project that was tricky and error prone to share around projects.

With recent releases, they've moved to something that resembles Ruby On Rails's "vendor" approach, where a sub directory contains a whole checkout of each dependency's code. This is actually a pretty major improvement (in part becuase it works better with Github, which is Go's primary distribution mechanism). But even with this change, managing a codebase over time is error prone. Unlike Maven and Nuget, there is no enforced concept of version releases (nor discipline around snapshotting) in Github. So if the maintainer of the library has poor discipline (or if there is code poorly tagged during a maintainer change), it can be difficult to get the exact version of a library you want with the exact bugfixes you need.

Google's response to this is, "We don't have this problem because everyone at Google always keeps /master clean and we basically never make breaking API changes." But if you talk to them internally, the reality is more what you'd expect. Sometimes a lot of time is lost fixing that.

Everything else I wanted to say (aka "Conclusions")

On balance, Go is a good environment for making executables. But a lot of why people like it stems from negative experiences they've had with scripting languages and their poor packaging story, Java and its problems keeping up with other managed language runtimes (and oh god its package process is just silly and antiquated).

You've already got cutting edge concurrency, static builds, a lightweight crossplatform runtime with CoreCLR, and pretty fast cross-compilation. For you, what you might find refreshing is how very clean and unified the Go langauge is. It is many things, but one thing it excels at appealing to is the pythonic there-is-one-way-get-in-line crowd. It is small, purpose-built, and singularly uncomplicated. C# has a "history" and "legacy feature support." Things like delegates that have fallen out of fashion now but are still lurking in the codebase or backing other more modern features.

If you want to try the concurrency model but don't know if you wanna commit to a whole new runtime, do try F# if you haven't yet. You can get great performance and the channel based concurrency out of it, and I think most people would agree its error handling is light years ahead of what Go offers.

If you'd like to try a totally new language with really cool concurrency semantics on a purpose-built runtime, can I recommend Nim-lang.org? Nim is amazing. It's got one of the most ambitiously cool ideas I've seen for micro-optimized concurrency code since reading Marlow's paper on Haxl for facebook.