C# 9.0: Top-level Statements. Or Should I Say: Hey, Where’s the Main Method?
C# 9.0 introduces many new language features, and with this blog post I start a little series to look at some of those new features. Let’s start in this post with top-level statements.
When you create a new Console application with C#, you get a lot of boilerplate code. Below you see the code of a new app with the name ThomasClaudiusHuber.ConsoleApp.
using System;
namespace ThomasClaudiusHuber.ConsoleApp
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine("Hello World!");
}
}
}
If you’re new to C#, this program will confront you with a lot of concepts:
- using directives
- namespaces
- blocks { }
- classes
- static methods
- void return type
- array parameters
- Console.WriteLine statement
Already in earlier versions of C# and .NET the namespace was optional. Also the args
parameter of the Main
method is optional. You could write the program like this without the namespace
block and the args
parameter:
using System;
class Program
{
static void Main()
{
Console.WriteLine("Hello World!");
}
}
This means, from our list, you can remove namespaces and array parameters:
- using directives
- namespaces
- blocks { }
- classes
- static methods
- void return type
- array parameters
- Console.WriteLine statement
C# 9.0, which comes out with .NET 5, brings all of this to the next level by allowing so-called top-level programs. That means you can write statements directly at the top-level of a file. There’s no need to define a class and a static Main
method. The code below shows a Hello World Console app written with C# 9.0:
using System;
Console.WriteLine("Hello World!");
Now, when you look at the list of features that we have here compared to the original Console app, it looks like below. Only the using directive and the Console.WriteLine statement are left:
- using directives
- namespaces
- blocks { }
- classes
- static methods
- void return type
- array parameters
- Console.WriteLine statement
This makes it clear that simple top-level programs are an easy way to start with C#. Beginners don’t have to learn all the different features from the beginning.
When you use .NET 5 for your project, it defaults to C# 9.0 as a language version. So, just ensure your project runs on .NET 5 by looking at the .csproj file and ensuring that the TargetFramework
is set to net5.0:
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net5.0</TargetFramework>
</PropertyGroup>
</Project>
To target .NET 5, you need the .NET 5 SDK. You can download and install the .NET 5 SDK from here.
Where’s the Main Method?
Good question. If you worked with .NET in the past, you learned that the entry of every .NET program is the static Main
method. But in a top-level program, that Main
method does not exist, as we have just this code:
using System;
Console.WriteLine("Hello World!");
So, where the heck is our Main
method? Let’s look what code gets generated by using the classic tool available, the Intermediate Language Disassembler, or short ILDASM (There are more powerful alternatives like ILSpy or dnSpy, but ILDASM does the job here).
If you’ve installed Visual Studio 2019, you find a Visual Studio 2019 folder in your start menu. In that folder is the Developer Command Prompt for Visual Studio 2019.
In the Developer Command Prompt, just write ildasm and press Enter to open the Intermediate Language Disassembler, as you can see in the screenshot below. Note that this works only in the Developer Command Prompt and not in a normal command prompt, as the Developer Command Prompt has the Path set accordingly, so that the ILDASM.exe is found.
In the Intermediate Language Disassembler, let’s open the .dll file of our Console app which uses C# 9’s top-level program feature. In the screenshot below I’ve opened the .dll file. You can see that a $Program
class and a static $Main
method was generated behind the scenes for us. This means there is still a static Main
Method, but it’s auto-generated if you create a top-level program where you don’t define that Main
method explicitly in your code.
When you double-click that $Main
method, you can see the Intermediate Language Code. You can see it in the screenshot below. The code shows you that the top-level Console.WriteLine
statement is actually added to that generated $Main
method. This means in other words: All your top-level statements are added to the generated $Main
method.
Commandline Arguments
When you look at the auto-generated $Main
method in the screenshot above, you can see that the $Main
method has also the conventional string[]
parameter for command line arguments. As the top-level statements that you write in a top-level program file are put into that auto-generated $Main
method, you can also access that string[]
parameter, that has conventionally the name args
. So you can write something like this in a top-level program to print out the command line arguments to the console:
using System;
if (args?.Length > 0)
{
foreach(var arg in args)
{
Console.WriteLine(arg);
}
}
There Can be Only ONE Main-Method File with Top-level Statements
Yes, there can be only one Main
method in .NET programs. If you define multiple Main
methods, you get a compile error unless you compile with the /main flag to define the entry point.
For a file with top-level statements, a $Main
method is generated and set as entry point. As there can be only one entry point for your .NET app, there can be only exactly one file in your project that has top-level statements. Let’s test this: In my Console app I added a Customer.cs file, in addition to the existing Program.cs file, and I tried to use a top-level statement there as well. In the screenshot below you can see that I get the error “Only one compilation unit can have top-level statements”, which means that you can use top-level statements only in a single file of your project.
Using Types and Namespaces
When you declare namespaces and/or types in your top-level program file, these declarations must be after all top-level statements, else you’ll get a compile error. For example, look at the file below. It declares the Friend
type before the top-level statements, which leads to an error on the first statement:
The correct order for that program is like shown in the code below. As you can see, the class Friend
is declared after the two top-level statements to create a Friend
and to print out the friend’s firstname to the console:
using System;
var friend = new Friend { FirstName = "Thomas" };
Console.WriteLine($"Hello {friend.FirstName}!");
class Friend
{
public string FirstName { get; set; }
}
When you look at the generated Intermediate Language code in ILDASM for the program of the code snippet above, you can see that the type Friend
is created as it would be in a separate file (You can also wrap the Friend
class in a namespace block if you want). This means the C# compiler takes the top-level statements that you have to define first, moves them to the generated $Main
method and then it compiles all the types in that top-level file as it would compile them in any other C# file.
Summary
I think top-level programs are a great addition to C#. It makes it quite easy to play with the language. And especially for simple projects it is great that you don’t have the additional namespace, class and Main
method in your code. .NET auto-generates all that boilerplate stuff for you behind the scenes, which is great. I like this feature.
I think the downside is that beginners might not really understand what happens behind the scenes. But is that necessary to start with C#? Maybe not if you have only a single file in your project. But if I explain C# to a new developer, I start with the Main
method, saying that this is the entry point of the program. This is something everyone understands. Your program starts exactly here! With a top-level file I have to say that the file with top-level statements is the entry point of the program. And now, what is simpler for a beginner:
a) finding the file with the top-level statements
b) finding the Main
method
Hm, if there’s only one file, it’s easy and it doesn’t matter too much, but with multiple files and without knowing the concepts of namespaces, classes and methods it’s hard to know what a top-level statement actually is. So I think it’s definitely b), finding the Main
method is easier than finding a file with top-level statements.
So, I think top-level statements are great to play with the language in a single file, and they are great for simple applications where you don’t have too many files. But if you consider becoming a .NET developer, I recommend you to start where everything begins: In the Main
method. :)
I hope this blog post helps you to understand the new top-level statement feature of C# 9.0 a bit better. In the next blog post you learn about init-only properties.
Happy coding,
Thomas
Comments (46)
You know, C# is very much in danger of disappearing up it’s own smart@ss?
Lot’s of ‘goodies’, lots of CS ‘purity’ – and no fun to just program in any more… No, I’m not a pro-programmer, just a fairly serious hobbyist, but moving further and further from C# at every iteration.
It’s no longer my go-to language for the small to intermediate size projects… very sad.
I still enjoy programming in C#. But I agree, the more features it gets, the harder the language is to read if you don’t know all its features.
What is the language that you use now for your smaller projects?
C# is just fine, there are more to evolve. I design software for web, mobile, and desktop, and it is becoming better since version 6.0 – good programmers are always studying, so complexity environment and language programming are natural.
Yes, that’s true Jefferson. Good programmers always want to learn new features. But learning also depends a bit on how much time you can actually spend on programming. I know many developers who have just one day per week, and then it’s sometimes hard to keep up with all the new stuff. I try to fill the void with some blog posts. Thanks for the feedback, happy to read you like C# and how it evolved, I like it too.
Nobody forces new and fancy features down your throat. You can continue to use C# 6 level of instructions and syntax if you like that.
Is C# now into the VB.NET realm after harvesting the Good Boys’ Programming Languages?
Good question Peter. I don’t think so, but I think the goal was to hide some stuff for beginners.
IMO this is a silly ‘feature’. The normal way really isn’t that difficult and there are concepts a newcomer will have to learn eventually anyway.
It’s definitely a feature where opinions exists from good and bad. I recommend to try it. For little applications I think I’ll use it.
Agreed 100%
I read the article and it is not clear to me who this change is for.
I don’t think the author knows either, since at the end he said it’s not for people who plan to become developers.
Is it for children learning to code?
Is it for hobbyists learning to code?
If so, are these markets in which c# wants to expand? It’s there a coherent plan being executed?
Is it maybe for automation or game development and various other scripting domains that might use c#?
This would make sense.
Good points. I don’t say it’s not for people who want to become developers. I say if you want to become a developer, you have to understand namespaces and classes too, and so the Main method is in my opinion a greater way to start. But people are different. I think top-level programs could also be a good way to start with the C# programming language, and if you like it, you can use it in any project.
Let’s see. People need to use it to find out. Personally, I like the feature.
This seems especially useful for demo specific functionality.
I’m doing a small presentation on “records” (another new function) and showing all different types via top level statements. This fits better on a screen without all the extra ceremony.
It can also used for other tiny applications, even microservices. But in most cases I would still use Main most likely.
Absolutely Michiel, that’s right. I also used Top-Level statements to get my 9yo daughter started with C#. It’s great when all you need is a Conosole.WriteLine statement and a using directive for the System namespace. Makes it easier for beginners. For bigger projects, I use Main explicitly.
Good write up on what I believe to be a pointless waste of time feature.
I program in C# for a living currently. Some of the early syntactic sugar made sense (nullable operators). But, lately, most of what they’ve been doing is adding stuff that can easily be done already. So they made writing a Main method easier… thereby encouraging people to code top-down, single methods that do way to much. OO is getting a bad wrap lately. OO is complicated IF the problem domain is complicated. But, that’s true of any programming paradigm. There is room for a .Net scripting language (like maybe built on Python or EMCA script… cause powershell is horrible IMHO), but C# isn’t it.
Thanks Eric. I also thought at first that it’s a waste of time feature. But actually, when you start using it for smaller applications, it feels good and right. I recommend to try it out before you make your decision. Maybe we’re just too used to the Main method.
I agree. This “feature” saves you absolutely no time at all. The Main() was always generated for you anyway, and whatever code you would have written in there you are still going to write anyway, only now as Top Level Statements. And it will only affect a single file in your whole project anyway (the one where Main() used to be). So its not more efficient to write code this way, its arguably not as easy to understand, and affects a single point in a single file. Just feels like a whole lot of Microsoft developer time was wasted implementing, documenting and releasing this when it is virtually useless.
It’s just syntactic sugar. If you don’t like it, simply use it the traditional way, since it’s still available. Why bother so much?
Totally agree Jonathan. For me the big question is if for example a new Console App will use that top-level syntax by default or not.
What type of nonsense is this; removing the “main()” method from a primary C# file among other such features?
All of these so called new features appear to add only one thing; ambiguity. And that is not how developers think and work. We need precision, not only visually with our tools but internally as well.
As one of the first commenters noted, the C# language is in danger of losing itself to the stupidity of its internal developers who seem to believe that if it can be implemented it should.
This is exactly what happened to the famed Clipper Compiler of the 1980s and early 1990s. The internals developers thought they were smart by exposing functions that were designed for the internals of the compiler to the general development community. It sowed so much confusion with few people understanding the need for such functionality that the entire company imploded…
Hey Steve, I recommend you to give it a try before you decide it’s totally nonsense. It actually feels good and right for small programs. My opinion about it was much like yours at the beginning before I tried it, and after that I thought: Well, it’s actually quite nice for smaller programms. Maybe also for beginners.
Hi Thomas…
Please forgive my long explanation below but in addition to being a software engineer I am also a military historian-analyst. These latter studies take me into the studies of trends that yield conflict.
>>>
I have watched with trepidation over the years how the C# language is being constantly extended with mostly “fluff” features. This makes it even more difficult for new developers to learn the language as its many new options put the state of the language in a sense of flux. New developers need foundational basics that do not change.
For experienced professionals, this make maintenance endeavors far more difficult as programs can now be written in a variety of ways, which will cause confusion instead of allowing a seasoned professional to get into the code quickly.
I have seen that the younger generations of developers accept such perceived amenities into their development environments as such developers appear to prefer such ambiguities. However, such a trend always has a price… and a large one.
For example, look what has happened to the English language over the years as the Internet has increasingly provided an environment whereby people no longer need to articulate correct pronunciation or written words as abbreviated ambiguity has set in. No one will dispute the deterioration in communicative capabilities as increasingly US high school graduates enter university with less than adequate language skills.
This may appear laughable to the development community but such a sense of the arcane and ambiguity has already begun to set in as development languages appear to be trending towards a form of hieroglyphics instead of the original English-like prose. So what if one can develop faster this way. Compilers do not really care weather the computer language is written out in a longer form or a shortened one. The only difference is the time it takes to parse such source code.
Microsoft has announced that they will no longer extend the VB.NET language. That is fine with me as I primarily now write in VB.NET (though I am completely fluent with C#) and there is nothing I cannot do in the language that a C# developer can.
For what all of us do in the MS Developer Community (with the exception of very specialized areas of development), both the C# and VB.NET languages havealways provided us with everything we require to do our jobs and none of these additional features have changed that in any way.
I still use ArrayLists for example, since I do not like adding carets into my coding that List collections enforce. using ArrayLists makes my code easier to read in general even though I well know that ArrayLists are not nearly as effiicient as the newer List collection. But in reality does it really matter? How many objects would I normally store in ArrayList; 20, 30, 50, maybe a hundred. What Human can tell the difference in performance between my ArrayList and another’s List collection?
People believe that societal change comes about from large events. This is not true. The so called large events come about as a result of a myriad of small, sociological threads all finally converging together.
It is the same thing here. It is not just the option to eliminate C#’s “main” method but yet another addition of a very small thread that down the road will eventually cause a growing chaos in such development as it already has in our web development endeavors. “Just try this new way of doing things; it will be easier and better for developing new and exciting web applications…” That is not working out very well these days based on all the documentation I have read about such development as well as my constant review of these new web development offerings.
Microsoft’s latest web offering with Blazor is simply a return to Classic ASP development, which I did a lot of before I transitioned all of my work over to ASP.NET. What is this? There are trends showing up in all of these new developments that do not appear to be supportive of stable development environments…
This looks like a feature intended to facilitate C# scripting. Consider a workflows type service like Azure DataBricks or LogicApps, they allow the designer/developer to add a block of Python to be executed as part of a workflow step. I expect we will be able to replace Python with C# or possibly VB.Net if this feature is implemented there.
Yes, I agree. C# scripting is already possible with .csx files, and with the top-level programs you can use a similar approach in normally compiled .cs file.
As a C# instructor I welcome anything that makes it easier to teach C# to beginners, and I think this will help. The argument “they will have to learn about classes and namespaces etc anyway” I think misses the point. When people haven’t heard of even variables or comments, it’s good to take things slowly, classes and namespaces can wait until the end of day 1 at least. Also the traditional Program class and Main method are static, and trying to teach a total newcomer static behaviour right up front is not realistic. In fact it makes it harder to teach people what classes are, because you explain them with reference to something simple like Employee or Book but then they see “class Program” and it undermines what you have said, because it doesn’t really match the definition you give when describing a class for the first time, because it’s static. So on the whole I welcome this, I can see it will make my teaching much easier, whereas most recent additions to C# have had the opposite effect.
One thing I would add though: if the goal really is to make it easier for newcomers to pick up the language, they could’ve gone the whole hog and made the “using System” implied too. It’s hardly likely you are going to teach a total newbie C# without wanting them to use Console or DateTime, so to all intents and purposes “using System” is boilerplate. You wouldn’t want to teach people to prefix Console or DateTime with “System.” as they’ll never do that in real life. I appreciate the issues it would bring but if the aim is learnability, I’d have got rid of that too.
Hey Dean, yes, I think it makes it quite easy to start for beginners. I like your thoughts about this new feature.
The big question to me is if for example the Console App project template will use a top-level file by default in the future, or if it will contain the Program class and the Main method as today. If it’s the latter, I’ll also start with the Main method in the future and explain top-level programs after that. Let’s see.
Thanks for sharing your thoughts!
A lucid, articulate, overview of changes which give me a sense of nausea. thanks, Bill
Well… removing “static void Main(string[] args)” – is it biggest problem C# has?!!
No, I don’t think so it is the biggest problem. But I guess it was easy to achieve and it might make learning C# simpler for beginners. Note that there are other new features in C# 9.0. I’ll write more blog posts about them in the upcoming weeks.
There are times when I use LINQPad for a short piece of code in order to avoid all the ceremony of writing in vs. this may change things.
Thank you
Yes, I agree. I think this is where top-level programs are really powerful: To try out a short piece of code. Thanks Avi
Great article Thomas!
But unless I’m missing something obvious, is this feature simply not more work for the dev? What’s the use case?
From what I can see, to use this feature I’d have to spin up a new project and then delete the boiler plate already added (Namespace, Main etc.)
Now if you could do “dotnet new console -type top-level” and get a slimmed down console app, that would be pretty useful for quick scripts.
Hi Dan, I agree to all that you wrote. You didn’t miss something obvious.
Cheers,
Thomas
One thing that nobody here mentioned yet is that this also allows simple C# programs to be simple. Yes, it is a great tool for simplifying the learning curve for new devs, but it also means a small program that does something simple can be written without the extra ceremony around it. Quite frankly, I’m having a hard time imagining ever writing `public static void Main(string[] args)` ever again.
I would like to see a template that starts you in the top-level statement model though. Either:
“`
System.Console.WriteLine(“Hello World!”);
“`
Or:
“`
using System;
Console.WriteLine(“Hello World!”);
“`
That would be better than using the current template and deleting it. I know they were working on updating templates for .NET 5 for VS 16.9. I don’t know if they’ll end up with that type of template. But I hope they do.
I think the point of this feature is actually to get rid of boilerplate, nothing else.
Regarding the template: Yes, could be that it will be changed in the future. Let’s see.
[…] Kanımca burada amaç çok daha net, tüm klişe çıkartılmış. Üst düzey ifadeler hakkında daha fazla bilgi edinmek için, bu makale çok kullanışlı. […]
I use LINQPad to play around and test the language features and statements. But now, I guess I can do the same without leaving the visual studio. Sounds great!
I started from page 15 of your blogs and now reached page 1. I read many articles that interest me. You are precise and to the point.
Thanks for sharing your knowledge.
We can remove using directives too. )
System.Console.WriteLine(„Hello World!“);
Yes, but then you have to explain full qualified class names. :-)
Global usings will avoid that, they’re coming with .NET 6.
With top level statements, how can you add a method to the Program class that is outside of Main? (The method would be static so it can be called from Main.) If I just add a method, it treated as a local method within Main.
If you don’t want it as a local function, then you have to explicitly create the Program class and the Main method.
Is there a setting or anything we can do so new Console application projects are started with an explicit Program class and Main method by default?
Yes, there’s a flag for the .NET CLI:
dotnet new console –use-program-main
In Visual Studio you also have a checkbox for this flag when creating a new console app.
this is like companies making oversimplified logo, i don’t like it
At least put the option to get the Main() back. this “new” form of top-level mumbo jumbo is ridiculous and is not consistent with object classes concept :(
The option exists today when creating a new project in Visual Studio. Also when creating a new console project from the command line, there’s an option you can use to get a regular main method in a Program class.
dotnet new console –use-program-main