.NET Development on Apple Silicon
During the last several days, I’ve been experimenting with an Apple M1 device from the perspective of a .NET Developer using a typical developer toolkit: .NET SDKs, Node, JetBrains Rider, other editors, and mobile emulators.
For .NET developers considering using an Apple M1 for their daily development machine, we’ll see what it is currently like to work on these devices. The goal is not to get everything to work, but to share the out-of-the-box experience developers will face in adopting an M1 chipset-driven products.
On November 1st, 2020, through a mesmerizing drone-driven presentation, Apple unleashed its first chip explicitly designed for its Mac product line. "Unleash" is an appropriate verb, as with the new chipset, Apple’s claims are to deliver performance and efficiency with no compromises.
M1 delivers up to 3.5x faster CPU performance, up to 6x faster GPU performance, and up to 15x faster machine learning, all while enabling battery life up to 2x longer than previous-generation Macs. — Apple
Apple’s claims are promising from a technical perspective, but what about current real-world usage? One of the current issues facing the transition to M1 is the architectural move from the x86 instruction set to the ARM instruction set. To make the transition smoother for everyone adopting these new devices, Apple has implemented a translation layer called Rosetta 2.
Existing Mac apps that have not been updated to Universal will run seamlessly with Apple’s Rosetta 2 technology. — Apple
Let’s give it a try!
My goal from the onset was to experience what the "average" developer would. I didn’t compile any special runtimes or have access to unique installers. Any developer out there will have access to the same environmental conditions. All experiences occurred with x86 translation via Rosetta 2.
Generally, .NET development comes in two choices: Mono and .NET Core. Developers can work with the .NET Framework through software development kits (SDKs). During this trial, I tested Mono, .NET Core 2.1, .NET Core 3.1, and .NET 5. All SDKs successfully built multiple solutions, from small sample Hello, World applications to complex OSS solutions.
I built projects primarily in Rider, and by using the .NET CLI from a terminal. From an anecdotal viewpoint, the experience was uneventful, which makes it remarkable. Code completion, package additions, and development functionality just worked. Build times were respectable but were slightly slower than my personal six-core i7 MacBook Pro. "Slower" is a relative term, and I believe most folks wouldn’t notice this difference as they did their daily work.
Within the .NET ecosytem, there are different development niches, the largest being ASP.NET, a web framework. While testing several web projects, the local development servers could start and service requests as they usually would. Similar to my experience building projects, it was what most developers would expect from a development environment.
Mobile development is another popular focus of .NET developers. The Xamarin SDK included with JetBrains Rider did not work, but a workaround uses the latest SDK version with Visual Studio for Mac, which Rider will recognize. The current
master branch of the SDK is broken, but the team is working to address the SDK issue.
During mobile development, I tested targeting Android and iOS devices with the iOS simulators working as expected. The Android emulator would begin to boot, but the device image would not load. During my testing with Android, I attempted both x86 and ARM images with no success. Experienced mobile developers will likely be using physical test devices, so this might not be a deal-breaker.
The biggest issue that might deter .NET developers is the current lack of debugger support for .NET Core SDK solutions. None of the major .NET editors could attach the debugger to a running process. The attempt to do so would throw a
StackOverflowException. Exceptions seem to originate from the kernel with a C method named
Not surprisingly, the debugger shipped with the Mono SDK runs without issue. Mono is designed to run C# on iOS and Android devices natively. Apple and OSS authors of Mono likely resolved issues facing the Mono runtime before Apple Silicon’s official launch.
Developers focusing on Xamarin, which still uses Mono, should have no debugging issues. While not tested, Unity game developers also use Mono, and I assume that will work as well. All promises of reliability are up in the air for Mono devs utilizing P/Invoke calls down to native system calls. Folks will need to test native system calls on a need-by-need basis.
For folks interested in performance, developer Hank G. has done extensive benchmarking utilizing .NET Runtime test harnesses. Hank G. found the Intel chips outperformed the M1 in some cases. In other instances, the opposite occurred. It’s important to keep in mind that the M1 chipset delivers an enjoyable performance experience at almost half the cost of its MacBook Pro predecessors. The mere fact that it can keep up in benchmarks in an unoptimized state is impressive alone. Few will notice the differences between M1 and Intel for daily usage, even during this Rosetta 2 period.
Thoughts around .NET focused development on an M1 device are generally positive. The development time experience of writing and compiling code is uneventful and yawn-inducing (in the right way – it "just works"!). Developers uncomfortable with bleeding-edge pain will likely think twice before purchasing an M1 device, that is unless minor releases of the SDKs address the debugger issues. Current Xamarin and Unity developers still using Mono will have an overall experience that works.
I’ve focused on .NET, but no one programs in a vacuum… What about dependencies?
Containerization has become a popular way of utilizing external dependencies, and Docker is the de-facto way many developers consume databases, cache providers, and more. Sadly, as of writing this post, Docker doesn’t yet work on the M1 chipset. There are always workarounds:
- Install dependencies locally on the host machine.
- Use remote devices or cloud services.
- Other virtualization approaches on the host.
These workarounds have their drawbacks, like network latency, excessive local resource utilization, general upkeep that containers don’t suffer from, but it does get developers working.
Docker has a preview build that supports Apple Silicon and is currently available to folks in the developer preview program. That said, .NET developers will likely struggle to find ARM container images for current dependencies like SQL Server for Linux or the .NET runtime itself. It’s important to remember how Docker containers work. They utilize calls to the host operating system, so calls need to use the ARM instruction set both inside and outside of containers. Since virtualization is occuring, Rosetta 2 cannot convert x86 containers to run on Apple Silicon.
Docker running on Apple Silicon is only the first step in supporting containerization enabled workflows. Solution vendors will need to build images that utilize ARM instruction sets.
Overall, I had a positive experience programming .NET on an M1 device. Apart from the .NET debugging issue and Docker currently not working, it’s impressive that almost everything seems to just work with Rosetta 2! And keep in mind, it’s not a matter of if there will come native support for specific development scenarios and .NET; it’s only a matter of when.