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

Share this post

Comments (30)

  • Steve Reply

    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.

    August 20, 2020 at 8:04 am
    • Thomas Claudius Huber Reply

      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?

      August 20, 2020 at 8:28 am
    • Jefferson Motta Reply

      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.

      August 20, 2020 at 3:04 pm
      • Thomas Claudius Huber Reply

        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.

        August 20, 2020 at 4:02 pm
  • Peter Adam Reply

    Is C# now into the VB.NET realm after harvesting the Good Boys’ Programming Languages?

    August 20, 2020 at 8:11 am
    • Thomas Claudius Huber Reply

      Good question Peter. I don’t think so, but I think the goal was to hide some stuff for beginners.

      August 20, 2020 at 8:25 am
  • Jacques Reply

    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.

    August 20, 2020 at 8:14 am
    • Thomas Claudius Huber Reply

      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.

      August 20, 2020 at 8:24 am
    • Fernando Reply

      Agreed 100%

      August 20, 2020 at 5:14 pm
  • Curious Reply

    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.

    August 20, 2020 at 10:56 am
    • Thomas Claudius Huber Reply

      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.

      August 20, 2020 at 12:12 pm
  • Eric Reply

    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.

    August 20, 2020 at 2:56 pm
    • Thomas Claudius Huber Reply

      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.

      August 20, 2020 at 3:56 pm
    • Brett Reply

      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.

      August 25, 2020 at 6:13 am
  • Jonathan Lazaro Reply

    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?

    August 20, 2020 at 3:25 pm
    • Thomas Claudius Huber Reply

      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.

      August 20, 2020 at 3:57 pm
  • Steve Naidamast Reply

    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…

    August 20, 2020 at 3:48 pm
    • Thomas Claudius Huber Reply

      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.

      August 20, 2020 at 4:06 pm
      • Steve Naidamast Reply

        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…

        August 20, 2020 at 5:13 pm
  • jlo Reply

    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.

    August 20, 2020 at 9:04 pm
    • Thomas Claudius Huber Reply

      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.

      August 20, 2020 at 10:28 pm
  • db Reply

    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.

    August 21, 2020 at 11:15 am
    • Thomas Claudius Huber Reply

      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!

      August 21, 2020 at 2:45 pm
  • Bill Woodruff Reply

    A lucid, articulate, overview of changes which give me a sense of nausea. thanks, Bill

    August 21, 2020 at 8:53 pm
  • Vincie Reply

    Well… removing “static void Main(string[] args)” – is it biggest problem C# has?!!

    August 24, 2020 at 11:31 am
    • Thomas Claudius Huber Reply

      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.

      August 24, 2020 at 12:40 pm
  • Avi Farah Reply

    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

    September 13, 2020 at 1:10 pm
    • Thomas Claudius Huber Reply

      Yes, I agree. I think this is where top-level programs are really powerful: To try out a short piece of code. Thanks Avi

      September 14, 2020 at 11:14 am
  • Dan Pedder Reply

    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.

    November 11, 2020 at 4:00 pm
    • Thomas Claudius Huber Reply

      Hi Dan, I agree to all that you wrote. You didn’t miss something obvious.

      Cheers,
      Thomas

      November 23, 2020 at 10:11 am

Leave a Reply

Your email address will not be published. Required fields are marked *

*

This site uses Akismet to reduce spam. Learn how your comment data is processed.