When writing programs in the C# programming language, you invariably need to write a lot of boilerplate code — even for simple console applications. Imagine that you want to write some code to test whether a library or an API is functioning properly. You might write a console application to accomplish this, but you’re nonetheless constrained to follow standard C# semantics. You must write your code inside the Main method.
Top-level programs, a new concept introduced in C# 9.0, allow you to write code for simple programs sans the need to write boilerplate code. Top-level programs are a great new feature that allows you to write cleaner, shorter, and simpler code. You can take advantage of top-level programs to explore new ideas. This article discusses how you can work with top-level programs in C# 9.0.
To work with the code examples provided in this article, you should have Visual Studio 2019 installed in your system. If you don’t already have a copy, you can download Visual Studio 2019 here. Note that C# 9.0 is available in Visual Studio 2019 version 16.9 Preview 1 or later, and in the .NET 5.0 SDK.
Create a .NET Core console application project in Visual Studio
First off, let’s create a .NET Core console application project in Visual Studio. Assuming Visual Studio 2019 is installed in your system, follow the steps outlined below to create a new .NET Core console application project in Visual Studio.
- Launch the Visual Studio IDE.
- Click on “Create new project.”
- In the “Create new project” window, select “Console App (.NET Core)” from the list of templates displayed.
- Click Next.
- In the “Configure your new project” window, specify the name and location for the new project.
- Click Create.
We’ll use this project to work with top-level programs in the subsequent sections of this article.
Top-level program example in C# 9.0
Let’s look at a before-and-after example of how top-level programs can eliminate boilerplate code. Before top-level statements in C# 9.0, this is the minimal code you’d write for a console application:
using System;
namespace IDG_Top_Level_Programs_Demo
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine("Hello World!");
}
}
}
When working with C# 9.0, we can avoid the noise and take advantage of top-level programs to write our code in a much simpler way. The following code snippet illustrates how you can take advantage of top-level statements to refactor the above code:
using System;
Console.WriteLine("Hello World!");
In either case, when the program is executed, you’ll see the string “Hello World!” displayed at the console window.
Use methods in top-level programs in C# 9.0
You can use methods with top-level programs. Below is a code example that illustrates how you can use methods with top-level programs.
System.Console.WriteLine(DisplayMessage("Joydip!"));
System.Console.Read();
static string DisplayMessage(string name)
{
return "Hello, " + name;
}
When you execute the above program, you should see the output “Hello, Joydip!” appear in the console window:
Use classes in top-level programs in C# 9.0
You can also use classes, structs, and enums in top-level programs. The following code snippet illustrates how you can use classes in top-level programs.
System.Console.WriteLine(new Author().DisplayMessage("Joydip!"));
System.Console.Read();
public class Author
{
public string DisplayMessage(string name)
{
return "Hello, " + name;
}
}
When you execute the above program, the output will be similar to Figure 1.
How top-level programs work in C# 9.0
So, how do top-level programs work exactly? What happens behind the scenes? Top-level programs are essentially a compiler feature. If you don’t write the boilerplate code, the compiler will generate it for you.
Refer to the following piece code we wrote earlier.
using System;
Console.WriteLine("Hello World!");
The code displayed below (generated using the SharpLab online tool) shows what the compiler-generated code would look like.
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!");
}
}
If you look at the compiler-generated code, you’ll see the [CompilerGenerated] attribute on top of the static class generated by the compiler.
Top-level programs are a great new feature in C# 9.0, whereby the compiler automatically generates the boilerplate code for you behind the scenes. Top-level programs are great for simple programs that don’t have too many files and dependencies. Note that only one file in your application may use top-level statements; otherwise the compiler throws an error.
One downside to top-level programs is that, if you’re new to C#, you might not be able to understand what’s happening in the code behind the scenes. A better way for beginners to learn C# will be using the Main method, and avoiding top-level statements until you understand how the Main method works. But those who have mastered Main will find top-level statements a very useful shortcut.
How to do more in C#:
- How to use pattern matching in C#
- How to work with read-only collections in C#
- How to work with static anonymous functions in C# 9
- How to work with record types in C#
- How to use implicit and explicit operators in C#
- Singleton vs. static classes in C#
- How to log data to the Windows Event Log in C#
- How to use ArrayPool and MemoryPool in C#
- How to use the Buffer class in C#
- How to use HashSet in C#
- How to use named and optional parameters in C#
- How to benchmark C# code using BenchmarkDotNet
- How to use fluent interfaces and method chaining in C#
- How to unit test static methods in C#
- How to refactor God objects in C#