The evolution of the C# language has introduced us to many new features and probably made a lot of folks re-evaluate how they write their apps. In the latest versions of .NET, nothing has been marked such a stylistic shift as the introduction of top-level statements.

In this short post, we’ll examine how to add properties to your Program instance to improve the readability of utility console applications.

Top-level statement files

When starting a new console application, you can create a Program.cs file and opt into the top-level statements style. The single line below is a valid .NET application.

Console.WriteLine("Hello, World");

At compile-time, the compiler generates the typical ceremony associated with traditional applications. Looking at our app’s low-level C# version, we’ll see the symbols we typically expect to see in a .NET app.

using System;
using System.Runtime.CompilerServices;

[CompilerGenerated]
internal class Program
{
  private static void <Main>$(string[] args)
  {
    Console.WriteLine("Hello, World");
  }

  public Program()
  {
    base..ctor();
  }
}

You’ll see that there is always a Program class and the expected entry point of static void Main.

Let’s get more complex.

Console.WriteLine($"Hello, {GetName()}");  
  
string GetName()  
{  
    return Environment.GetCommandLineArgs()[1];  
}

What does this look like now?

using System;
using System.Runtime.CompilerServices;

[CompilerGenerated]
internal class Program
{
  private static void <Main>$(string[] args)
  {
    Console.WriteLine(string.Concat("Hello, ", Program.<<Main>$>g__GetName|0_0()));
  }

  public Program()
  {
    base..ctor();
  }

  [NullableContext(1)]
  [CompilerGenerated]
  internal static string <<Main>$>g__GetName|0_0()
  {
    return Environment.GetCommandLineArgs()[1];
  }
}

The method attached to the end of the file becomes an internal static method on the Program. Can we add a property? In this case, a property would be much more readable than a method.

// not valid c#
Console.WriteLine($"Hello, {Name}");  
  
string Name => Environment.GetCommandLineArgs()[1];

This syntax does not work. Still, we can add a Name property to our Program file.

Adding the property with a partial class

How do we get top-level properties into our top-level statements file? Well, it’s pretty straightforward. The Program class can be extended using the partial keyword. Let’s do that.

Console.WriteLine($"Hello, {Name}");

partial class Program
{
    static string Name => Environment.GetCommandLineArgs()[1];    
}

Now, we can add scoped properties to our top-level statement files. Returning to our low-level C#, we can see what the compiler is doing.

using System;
using System.Runtime.CompilerServices;

internal class Program
{
  private static void <Main>$(string[] args)
  {
    Console.WriteLine(string.Concat("Hello, ", Program.Name));
  }

  [Nullable(1)]
  private static string Name
  {
    [NullableContext(1)] get
    {
      return Environment.GetCommandLineArgs()[1];
    }
  }

  public Program()
  {
    base..ctor();
  }
}

You can also move your partial Program class to another file if you are going for that minimal aesthetic entry point look. One thing to note is that all properties must be static, as the properties are referenced in the static void Main method.

I hope this post helps, and as always, thanks for reading. Cheers.