Run independent C-Sharp files instantly with 'dotnet run'
I still remember the first time I learned C# back in college. At the time, we were working with .NET Framework 4.8, and let me tell you, writing C# wasn’t just a matter of typing some code and running it. While the language itself was fairly easy to write and understand, getting everything to work involved much more than just the C# code.
There was the whole setup process: solution files, project files (.csproj), etc. They were scaffolded automatically, which helped, but as a beginner, I had no idea what they actually meant. I was focused on learning the C# language itself, and these additional components felt like an obstacle I didn’t fully understand.
Eventually, I did figure them out. But at the time, just getting something simple like “Hello World” to appear on the screen felt far more complicated than it needed to be. Compared to JavaScript or Python, where you could open a terminal or browser console and start coding instantly, C# required a lot more setup before you saw any results.
Of course, these setup files I’m talking about are still required today, or are they… ? 🤔 In this blog post, I’m going to walk you through something new that will be shipping with .NET 10: dotnet run app.cs
, and some of the additional steps that Microsoft took to get us where we are now. Or at least, that’s where we’ll start. Because as it turns out, dotnet run app.cs
might not be the entire story… 📖
A look at the past
Let’s go a few years back, to 2019. I am going to write a simple ‘hello world’ console application. I select the console application template, and the following folder structure will be generated out for me:
When opening the Program.cs
this is what I see:
namespace HelloWorld.Terminal
{
public class Program
{
public static void Main(string[] args)
{
Console.WriteLine("Hello, World!");
}
}
}
To an experienced developer, this all looks pretty standard. But for someone just starting out, it can already feel overwhelming.
- Wait… what’s this
Main
method supposed to do? - Huh? A namespace? What even is that?!
- Whoa okay, I just clicked on that project file, and I have no clue what any of this means.
Feel the pain? You’re not alone. Most of the people starting out with programming will never choose C# as their first programming language. They will often choose a scripting language like Python, which is considered more friendly towards beginners.
Top level statements
Let’s go forward in time a little, to November 2020. This is when .NET 5 with C# 9 was introduced. With that introduction came the addition of top level statements. This feature made it possible to instead of writing all of the above code for a Hello World to appear on the screen, to just write the following:
Console.WriteLine("Hello, World!");
This was revolutionary at the time! The idea of eliminating all that boilerplate code from the start, amazing right!? Well… maybe don’t get too excited just yet. For starters, this simplification only applied to the Program.cs
file. It didn’t mean you could suddenly write all your files without namespaces or class definitions. Even in that one file where it did work, it was ultimately just syntactic sugar. If you looked at the low-level C# code being generated, you’d still find the compiler using a namespace and a Main method behind the scenes.
Since .NET 6, these top level statements are automatically used when using a template. 💡
Alright, so with this change, we were definitely moving in the right direction. But we still had the project and solution files hanging around. And it would stay like that, for several more years. Until…
‘dotnet run app.cs’ is here!
With .NET 10 we’re getting a cool new feature that will allow us to run C-Sharp files completely independent. Meaning no project setup and/or solution files required!
As of writing this blog post, .NET 10 has not yet had a stable version released. To make use of the functionality that I’ll be talking about, you will need to install preview 4 or above. You can install it here.
So now, given we have this HelloWorld.cs
file:
Console.WriteLine("Hello, World!");
I’m currently writing these files in VS Code. To get IntelliSense to work, you’ll need to be on a pre-release version of the ‘C#’ and ‘C# Dev Kit’ extensions. When .NET 10 is out, a pre-release version of these extensions is then not required anymore of course.
We can now execute the file by entering the following command:
> dotnet run HelloWorld.cs
You’ll notice that the execution is still rather slow, even after you already did a cold run. Since it’s currently running a preview build, I still expect them to make improvements. 🔨
This is a simple example, but let’s say you want to run an API this way or use NuGet packages, you can! Microsoft has introduced a new way to reference packages without the need of a project file. Here I’m referencing the Microsoft SDK that’s needed for the API:
#:sdk Microsoft.NET.Sdk.Web
var builder = WebApplication.CreateBuilder();
var app = builder.Build();
app.MapGet("/", () => "Hello, world!");
app.Run();
You can even include third party packages with this same notation by using #:package
. ☝🏻
You also have the option to convert these C# files into a full C# project with a solution file attached. You can do this by running dotnet project convert {name-of-your-app}.cs
.
Microsoft has also made their own detailed blog post about it. You can find it here.
With this new feature, Microsoft clearly wants to make it easier to get started with C#, while also stepping more into the scripting world. I definitely see this as a better way to start learning the language, or even just to quickly showcase or try out something new 🧪.
I’m still somewhat skeptical about the scripting side of things. I don’t expect people to stop using Python, JavaScript, or even PowerShell anytime soon. That said, this could appeal to those who are just starting to explore their first programming language. Only time will tell how it plays out.
So yeah, great stuff right? Well hang on a moment…
The forgotten tool
I started doing some digging, and apparently there is a tool out there called dotnet script. So what does this tool do exactly?
In a nutshell: dotnet script is a .NET CLI global tool that allows you to write and run C# code as simple script files (*.csx), without needing the usual project structure (.csproj, Main() method, build steps). You can run scripts directly, include NuGet packages inline, use it cross-platform, and even debug scripts in Visual Studio Code!
So given a HelloWorld.csx
file:
Console.WriteLine("Hello, World!");
We can execute that file using the following command
dotnet script HelloWorld.csx
Well doesn’t sound this familiar? 🤔 In fact, the commits from this GitHub repo can be tracked back all the way to 2016. That’s almost 10 years ago at the time of writing this blog. Ironically, this tool was available to use even before Microsoft introduced the concept of top level statements.
Looking closer, there are some small differences, but in essence, this tool, and the ’newly’ introduced concept of Microsoft achieve the same thing. I would expect that in this specific case Microsoft took some inspiration from dotnet script
.
So, what does this learn us? Not everything that that gets announcement as something ’new’, is actually new. Sometimes, concepts and solutions are just repurposed and recycled. Does it mean that dotnet run app.cs
cannot be good? Of course it can be good, but when things like this get announcement, we should always take it with a grain of salt, and don’t give them too much credit up front. It might be the case that somebody else came up with the idea first. 💡