Special Offer: My C#/.NET Bootcamp Course is out now. Get 10% OFF using the code FRIENDS10.

C# requires a lot of boilerplate code, even for simple console applications. We need a namespace, a Program class, and even a static Main method. 

How brilliant would it be if we could create a new file and start writing our code?

Let’s find out how top-level statements in C# 9 solve this problem and why we should use them.

C# 9 introduces top-level statements. Top-level statements allow us to write C# statements without explicitly creating a namespace, a class, and a static Main method as an entry point to our program.

Let’s take a look at the default template of a console application. By the way, it doesn’t matter what project type we use. Every C# project type has the same entry point. Somewhere in the project, there is always a static Main method to be found.

using System;

namespace ConsoleApp
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("Hello World!");
        }
    }
}

As we can see, we have a namespace definition, a class, a static void Main method with a string array parameter, and a lot of curly braces that define code blocks.

Use Top-Level Statements

Now let’s see how we can refactor this code to use the top-level statements C# 9 provides. First, let’s take the Console.WriteLine statement and move it to the top of the file. Next, we remove everything else below this statement.

using System;
Console.WriteLine("Hello World!");

If the program compiles, you should see the Hello World output when running the application.

If the program doesn’t compile, make sure you are using .NET 5 and C# 9. If you aren’t sure or need to set up .NET 5 first, check out how to setup and use C# 9.

We can go a step further and remove the using statement on the program’s first line and fully qualify the call to the WriteLine method.

System.Console.WriteLine("Hello World!");

Top-level statements make C# feel simpler, and they help create short example programs to demonstrate language features. Also, it should be more comfortable for beginners to learn all the language features one-by-one.

What happens behind the scenes?

But how does all that happen behind the scenes? Top-level statements are essentially a compiler feature. That means that even if we don’t write the boilerplate code ourselves, the compiler does the work for us, and we end up with the same old structure.

Let’s verify that by looking at what intermediate language code gets generated from a top-level statement definition.

using System;
using System.Diagnostics;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Security;
using System.Security.Permissions;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.Default | DebuggableAttribute.DebuggingModes.DisableOptimizations | DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints | DebuggableAttribute.DebuggingModes.EnableEditAndContinue)]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("0.0.0.0")]
[module: UnverifiableCode]
[CompilerGenerated]
internal static class <Program>$
{
    private static void <Main>$(string[] args)
    {
        Console.WriteLine("Hello World!");
    }
}

As we can see, the compiler generates a static class, a static void Main method including the args parameter defined as a string array. It puts the top-level statement into the body of the Main method.

The only thing that’s missing from the generated code is the namespace definition. But namespaces are optional. Although the default project template creates a namespace for your program, and we’re all used to work with namespaces, they are not required. 

That’s why the compiler does not generate a namespace for our program in this case.

Using command-line arguments

Can we use command-line arguments in programs that use top-level statements? Yes, a convention allows us to access command-line arguments even without having a static Main method.

Let’s open the project settings and set a program argument in the Debug tab. Don’t forget to save the project file.

System.Console.WriteLine("Hello World: " + args[0]);

Back in the Program.cs file, we write another WriteLine statement and output the first command-line argument to the console. We start the program, and we see the defined argument in the console output.

Defining and using Types

When working with top-level statements, we can define and use types. What’s important is that the type definitions have to be placed below the top-level statements

C# allows us to have as many top-level statements in our file that we like, but we need to make sure that all top-level statements are placed above everything else.

Let’s create a Person class with a Name property and use it in our program.

var name = "John Wayne";
System.Console.WriteLine("Hello World: " + name);

class Person
{
    public string Name { get; set; }
}

As we can see, the program compiles, and the output in the console looks like what we expected.

Are there limitations?

The only limitation I experienced while working with top-level statements is that you can only have top-level statements in a single file

It makes perfect sense because the compiler still needs to know where the program’s entry point is. 

If we had two files containing top-level statements, the compiler wouldn’t know which file contains the application’s starting point.

Conclusion

Top-level statements are a compiler feature introduced in C# 9. Top-level statements allow us to write short and concise C# programs. We can either use them to demonstrate specific language features or make it simpler to get started with C#.

Some people might argue that hiding the concepts of namespaces, classes, and code blocks might not be the best strategy to learn C#.

My personal opinion is that top-level statements give us more flexibility. Nobody forces us to use them, but if we have a use case where it makes sense to use them, we can do it.

It is a small feature, and it does not revolutionize how we write our C# programs, but it opens up new opportunities and adds another tool to our toolbox.

After learning about top-level statements, we aren’t surprised if we see code like this in the wild.

Let me know in the comments below what your opinion on top-level statements is and if you will use them in your projects or not.

Don’t forget to like the video so that other developers can learn about top-level statements too, and subscribe to learn more about .NET development.

 

Claudio Bernasconi

I'm an enthusiastic Software Engineer with a passion for teaching .NET development on YouTube, writing articles about my journey on my blog, and making people smile.