Some details about the Top Level Program in C# 9
By: Jung-Hyun Nam
Tags: Csharp, Dotnet, Programming, Coding

After launching .NET 5 and C# 9, I was utterly obsessed with the Top Level Program (a.k.a. TLP) feature. TLP delivers quick and fast prototyping for the .NET application and makes the most immediate entries for my work.

But TLP is just syntax sugar, not a complete solution. So there are little details you should know before choose TLP.

Thread Apartment Model

This detail is a rare case but critical for the Windows application developers. Usually, the Main method does not require particular attributes, excluding the Windows desktop application area. That thing is the thread apartment model, which impacts the Windows Forms or WPF application.

If you make your entry point to TLP, it will hard to specify the threading model. In my experience, configure the thread apartment model with the Thread class usually does not work well. If you do not configure the threading model to single-threaded, the Windows Forms application will not perform correctly, especially using WebView2.

So if you want to use the Windows Forms in your application, you may revert your entry point into the Main method again.

Write your code tidy up for others.

TLP helps you code faster, but your code can messy. Because TLP allows you to mix or shuffle the declaration orders between the logical code and the member function declarations.

For example, the below code is valid and compilable.

using System; int x, y; void TestFuncA() => x = 1; void TestFuncB() => y = 2; void Calculate() => Console.WriteLine(x + y); TestFuncA();

TestFuncB();

Calculate();

Also, this code works.

using System; int x, y; void TestFuncA() => x = 1; TestFuncA(); void TestFuncB() => y = 2; TestFuncB(); void Calculate() => Console.WriteLine(x + y); Calculate();

If you don’t tidy up your code, it makes others read difficult to read. So this is a reason that you’d better avoid large and complex code when you use TLP.

The args, hidden variable

You can write your simple application with the argument input. TLP hides the args variable from your sight, but it still exists. So you can reference the local variable args as you before.

So you can write your code like below.

// This sample code requires the Bullseye package. using System;

using System.Threading.Tasks; using static Bullseye.Targets; int x = default, y = default; async Task Banner()

{

await Console.Out.WriteLineAsync("Hello, World!");

} async Task ReadX()

{

int.TryParse(await Console.In.ReadLineAsync(), out x);

} async Task ReadY()

{

int.TryParse(await Console.In.ReadLineAsync(), out y);

} async Task WriteResult()

{

await Console.Out.WriteLineAsync($"{x + y}");

} Target("banner", Banner);

Target("read-x", ReadX);

Target("read-y", ReadY); Target("calc-result", DependsOn("read-x", "read-y"), WriteResult);

Target("default", DependsOn("banner", "calc-result")); await RunTargetsAndExitAsync(args);

Limited features

In contrast to my belief, TLP does not support some features. For example, you can declare the static local function but not the static local variable. So if you write the static local function, your code should contain an isolated logic (such as an algorithm). But you can call functions from your entry-point code.

Another missing feature is, TLP does not emit your Main method code as a static class. In my opinion, the local function is a non-static class member, so there is no need to preserve non-static members when we use the TLP feature. It’s a shame that the extension method cannot exist in the TLP code.