Top-level statements are a new feature introduced in C# 9 that allows you to write code directly at the root of a file, without having to wrap it in a class or a method. This means you can write a complete C# program with just a few lines of code, without the need for a Main method, which is the traditional entry point for C# programs. Top-level statements make it easier to write small and concise programs, such as scripts, code snippets, and one-off applications. They also improve the readability and maintainability of your code by reducing the boilerplate code and indentation levels.

The Evolution of C# Program Structure

Before C# 9, every C# program had to have a Main method that served as the entry point for the application. The Main method had to be declared inside a class, which had to be inside a namespace. For example, the following code is the typical structure of a C# program that prints "Hello World" to the console:

using System;

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

As you can see, this structure requires a lot of code and ceremony for a simple task. You have to import namespaces, declare classes and methods, and use curly braces and semicolons. This can sometimes be verbose and cumbersome, especially for small programs that don't need much functionality or complexity. It can also make it harder to read and understand the code at a glance.

Introducing Top-Level Statements

Top-level statements are a compiler feature that allows you to write code directly at the root of a file, without having to put it inside a class or a method. This means you can write executable code as soon as you open a file, without any extra syntax or structure. For example, the following code is a valid C# program that prints "Hello World" to the console using top-level statements:

Console.WriteLine("Hello World");

As you can see, this code is much shorter and simpler than the previous example. You don't need to import any namespaces, declare any classes or methods, or use any curly braces or semicolons. You just write the code you want to execute as if you were writing it in an interactive shell or a script file. The compiler will automatically generate a class and a Main method for you behind the scenes, so you don't have to worry about them.

Top-level statements allow you to focus on the logic and functionality of your program, rather than the structure and syntax. They also make your code more readable and understandable by eliminating unnecessary noise and clutter.

Advantages of Top-Level Statements

Top-level statements have several benefits that make them useful for writing small and concise programs in C#. Some of these benefits are:

Getting Started with Top-Level Statements

To use top-level statements in your C# programs, you need to have .NET 6 installed on your machine. You can download it from here. You also need an editor that supports C# 9 or higher, such as Visual Studio 2022, Visual Studio Code, or Rider.

To create a new console application project using top-level statements, you can use the dotnet new command with the console template:

dotnet new console -o MyProject

This will create a folder named MyProject with two files: Program.cs and MyProject.csproj. The Program.cs file will contain the following code:

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

See this guide for more information.

The MyProject.csproj file will contain the following XML:

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net6.0</TargetFramework>
  </PropertyGroup>
</Project>

You can now open the Program.cs file in your editor and start writing code using top-level statements. You don't need to modify the MyProject.csproj file unless you want to change some project settings or add some dependencies.

There are some requirements and limitations that you should be aware of when using top-level statements:

Code Examples

To demonstrate how to use top-level statements in C#, let's look at some code examples that perform common tasks. For each example, we will compare the code length and clarity with an equivalent program using the traditional Main method.

Printing to the Console

To print a message to the console, you can use the Console.WriteLine method. For example, to print "Hello World", you can write:

Console.WriteLine("Hello World");

This code is one line long and very easy to read and understand. In contrast, the equivalent program using the Main method would be:

using System;

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

This code is 11 lines long and contains a lot of unnecessary syntax and structure. It is also harder to read and understand at a glance.

Basic Calculations

To perform basic calculations, you can use arithmetic operators and literals. For example, to calculate the area of a circle with a radius of 10, you can write:

double radius = 10;
double area = Math.PI * radius * radius;
Console.WriteLine($"The area of the circle is {area}");

This code is three lines long and clearly shows the logic and the result of the calculation. In contrast, the equivalent program using the Main method would be:

using System;

namespace CircleArea
{
    class Program
    {
        static void Main(string[] args)
        {
            double radius = 10;
            double area = Math.PI * radius * radius;
            Console.WriteLine($"The area of the circle is {area}");
        }
    }
}

This code is 13 lines long and contains a lot of unnecessary syntax and structure. It is also harder to read and understand at a glance.

Reading User Input

To read user input from the console, you can use the Console.ReadLine method. For example, to ask the user for their name and greet them, you can write:

Console.Write("What is your name? ");
string name = Console.ReadLine();
Console.WriteLine($"Hello, {name}");

This code is three lines long and clearly shows the interaction with the user. In contrast, the equivalent program using the Main method would be:

using System;

namespace Greeting
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.Write("What is your name? ");
            string name = Console.ReadLine();
            Console.WriteLine($"Hello, {name}");
        }
    }
}

This code is 13 lines long and contains a lot of unnecessary syntax and structure. It is also harder to read and understand at a glance.

Interacting with Assemblies and Namespaces

Top-level statements can interact with assemblies and namespaces just like regular C# programs. You can import namespaces using using directives, reference external libraries using assembly references, and declare types and namespaces using type definitions and namespace declarations.

Using Directives

You can use using directives to import namespaces that contain types or members that you want to use in your code. For example, to use the System.Text namespace, which contains classes for working with text, you can write:

using System.Text;

StringBuilder builder = new();
builder.AppendLine("Hello");
builder.AppendLine("World");
Console.WriteLine(builder.ToString());

You have to place using directives before any top-level statements in your file. You can't place them inside or after top-level statements.

Assembly References

You can reference external libraries that contain types or members that you want to use in your code by adding assembly references to your project file. For example, to use the Newtonsoft.Json library, which contains classes for working with JSON data, you can add the following line to your MyProject.csproj file:

<ItemGroup>
  <PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
</ItemGroup>

This will download and install the Newtonsoft.Json library from NuGet, which is a package manager for .NET. You can then use the Newtonsoft.Json namespace in your code. For example, to parse a JSON string and print its properties, you can write:

using Newtonsoft.Json;

string json = "{\"name\":\"John\",\"age\":30,\"pets\":[\"dog\",\"cat\"]}";
dynamic obj = JsonConvert.DeserializeObject(json);
Console.WriteLine($"Name: {obj.name}");
Console.WriteLine($"Age: {obj.age}");
Console.WriteLine($"Pets: {string.Join(", ", obj.pets)}");

You can also use the dotnet add package command to add a package reference from the command line. For example, to add the Newtonsoft.Json library, you can run:

dotnet add package Newtonsoft.Json

You can find more information about using NuGet packages in .NET 6 projects here.

Type Definitions and Namespace Declarations

You can also declare your own types and namespaces in your code using type definitions and namespace declarations. For example, to define a class named Person and a namespace named MyNamespace, you can write:

namespace MyNamespace
{
    class Person
    {
        public string Name { get; set; }
        public int Age { get; set; }
    }
}

You can then use your types and namespaces in your top-level statements or in other files. For example, to create an instance of the Person class and print its properties, you can write:

using MyNamespace;

Person person = new Person { Name = "Alice", Age = 25 };
Console.WriteLine($"Name: {person.Name}");
Console.WriteLine($"Age: {person.Age}");

You have to place type definitions and namespace declarations after any top-level statements in your file. You can't place them before or inside top-level statements.

Compilation and Execution

Top-level statements are compiled and executed by the C# compiler just like regular C# programs. The compiler will generate a class named <Program> and a method named <Main> for you behind the scenes, and wrap your top-level statements inside them. For example, if your Program.cs file contains the following code:

using System;

Console.WriteLine("Hello World");

The compiler will generate something like this:

using System;

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

The compiler will also generate a unique name for the assembly that contains your program, such as MyProject.dll. You can change this name by editing the <AssemblyName> element in your project file.

To compile and run your program, you can use the dotnet run command from the command line. For example, to run the program in the MyProject folder, you can run:

dotnet run --project MyProject

This will output:

Hello World

You can also use the dotnet build command to compile your program without running it. This will create a binary file in the bin folder of your project. For example, to build the program in the MyProject folder, you can run:

dotnet build --project MyProject

This will create a file named MyProject.dll in the bin\Debug\net6.0 folder of your project.

There are no significant performance implications or differences in compilation when using top-level statements compared to traditional programs. The compiler will optimize and emit the same IL code for both cases.

However, top-level statements are not suitable for all types of projects or scenarios. They are primarily intended for small and simple programs that don't need much structure or complexity. For larger and more complex applications, you should use classes and methods to organize your code and logic.

Best Practices and Use Cases

Top-level statements are a convenient and powerful feature that can help you write small and concise programs in C#. However, they are not a replacement for classes and methods, but rather a complement to them. You should use them wisely and appropriately depending on your context and scope.

Some best practices for when to use top-level statements versus traditional structures are:

Some scenarios where top-level statements are particularly beneficial are:

Potential Drawbacks and Considerations

Top-level statements are not without their drawbacks and considerations. Some of the potential downsides of using top-level statements are:

Conclusion

Top-level statements are a feature of C# 9 that allows you to write code directly at the root of a file without having to wrap it in a class or a method. They can simplify your program structure and improve your code readability and maintainability. They are especially useful for writing small and concise programs, such as scripts, code snippets, and one-off applications. However, they are not suitable for all types of projects or scenarios. You should use them wisely and appropriately, depending on your context and scope. I hope this article has helped you understand what top-level statements are, how they work, and what are their advantages and disadvantages. I encourage you to experiment with top-level statements in your C# projects while being mindful of their limitations and best practices.

Refer to the below articles for more information:

Happy coding! 😊