Top-level programs in C# 9.0

C# 9.0 comes with nice new feature called top-level programs. It’s something that teachers of beginner classes will love for sure. Imagine – you start teaching C# with only two lines of code on screen. All missing code is generated by compiler. This blog post introduces top-level programs and shows some secrets of new C# compiler.

Classic console application

Here’s the classic console application. We have seen it thousands of times and we know what it does and how it works.

using System;

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

When students see this they usually have more than one question:

  1. What is doing “using System”?
  2. What is “namespace”?
  3. What is string “string[] args”?

Besides question there’s too much noice for them and all work on whiteboard happens inside Main() method.

What is top-level program?

With C# 9 we can skip all the noise and use top-level programs. Same code with this new feature looks like this.

using System;

Console.WriteLine("Hello World!");

Just two lines, no spacing and all the screen space is for my examples. Great!

Of course, I must have using directives but here’s the little trick to calm youngsters down.

using System; // let's talk about it later

Console.WriteLine("Hello World!");

Not only for teachers. Top-level programs can be used also in practice when writing real code. It is perfect when writing simple utility applications that doesn’t have much code.

Top-level programs are C# language feature and it doesn’t come down to Common Language Runtime (CLR). C# compiler produces Program class and Main() method (almost) like it was before.

But what about args argument of Main() method? It’s magically here and available in top-level programs. Here’s how to write out all arguments of program.

for(var i = 0; i < args.Length; i++)
{
    Console.Write(i);
    Console.Write(" ");
    Console.WriteLine(args[i]);
}

Using methods in top-level programs

We can write methods too. Notice that methods in top-level programs must be static.

using System; // let's talk about it later

Console.WriteLine(SayHello("students"));

static string SayHello(string name)
{
    return "Hello, " + name;
}

It’s interesting to see how methods in top-level programs look like functions in functional languages.

Using classes in top-level programs

We can also use classes and other things like structures, enums etc. The next code sample demonstrates primitive greeter class.

using System; // let's talk about it later

var greeter = new Greeter();

var helloTeacher = greeter.Greet("teacher");
var helloStudents = SayHello("students");

Console.WriteLine(helloTeacher);
Console.WriteLine(helloStudents);

static string SayHello(string name)
{
    return "Hello, " + name;
}

public class Greeter
{
    public string Greet(string name)
    {
        return "Hello, " + name;
    }
}

In this point things may get a little bit messy as there are methods and classes coming one after another.

Order matters! In top-level programs type definitions must come after top-level statements. It means after everything that goes to Program class.

Top-level programs after compiling

Classes in top-level programs are not particularly interesting as they are compiled like usual. But things get interesting with Program class and Main() method. Here’s the decompiled code of sample with greeter class.

using System;
using System.Runtime.CompilerServices;

[CompilerGenerated]
internal static class <Program>$
{
    private static void <Main>$(string[] args)
    {
        string str1 = new Greeter().Greet("teacher");
        string str2 = <Program>$.<<Main>$>g__SayHello|0_0("students");
        Console.WriteLine(str1);
        Console.WriteLine(str2);
    }

    internal static string <<Main>$>g__SayHello|0_0(string name)
    {
        return "Hello, " + name;
    }
}

Important things to notice:

  • Program class has CompilerGenerated attribute
  • Program class, Main() method and SayHello() method have obfuscated names that make it impossible to refer to them in .NET languages.
  • All top-level code members are in private or internal scope and invisible to other libraries.

Of course, it doesn’t stop us using obfuscated names if we use reflection. But I really don’t see any use case where we would need it and where we really cannot use some other approach.

Wrapping up

Top-level programs is nice C# language feature that cleans up Main() method of Program class by generating class and method automatically. All we have to do is to write the code and build it. Top-level programs have different Program class and Main() method as these are compiler generated. These two are not directly accessible from other libraries although we can do everything we want with reflection. Practical use cases for top-level programs are teaching coding to beginners and writing simple utility applications.

Gunnar Peipman

Gunnar Peipman is ASP.NET, Azure and SharePoint fan, Estonian Microsoft user group leader, blogger, conference speaker, teacher, and tech maniac. Since 2008 he is Microsoft MVP specialized on ASP.NET.

    8 thoughts on “Top-level programs in C# 9.0

    • Pingback:Dew Drop – September 3, 2020 (#3268) | Morning Dew

    • Pingback:The Morning Brew - Chris Alcock » The Morning Brew #3062

    • September 4, 2020 at 9:13 am
      Permalink

      Is it possible to use C#9 features yet in Visual Studio/Preview?

    • September 4, 2020 at 9:14 am
      Permalink

      I don’t think that this is a nice feature. In the future there will be many issues on stack overflow with people who will learn C# language and didn’t know about that generation under the hood. It’s nice feature for matured programmers but not on the beginner level. I think that it should be enabled with special attribute or something like that to prevent that type of development by default.

    • September 4, 2020 at 9:30 am
      Permalink

      Its a dumb idea, if a main entry point is too confusing to someone maybe they should try learn knitting, or cleaning toilets. Making the language retard friendly just means more retarded programs. The idea that everyone can learn to code is like saying everyone can write a book. While technically true it will produce a lot of books no one wants to read.

    • September 4, 2020 at 10:29 am
      Permalink

      Paul, yes you can do it with Visual Studio 2019 Preview and latest .NET 5 SDK.

      Question to LordWabbit – why do you call students and n00bies retards? After getting into coding they will do big things in future. Don’t be so pessimistic :)

    • December 24, 2020 at 12:10 am
      Permalink

      I’m not calling students or noobies retards, I’m saying if someone thinks that starting at the beginning (entry point) is too confusing maybe they should pick another avenue to express themselves. This whole “make things easier” paradigm has been tried before. Do you remember COBOL? BASIC? Yet here we are, clearly it didn’t work. While we are at it, why don’t we start a “build a bridge” or a “build your own house” exercise and see how that goes. All we need to do is remove all the pesky difficult stuff, like strength of materials, the law of physics, maths. What could go wrong? We could replace all those pesky designs and complications with a series of simple stencils everyone can use, they can go on to build big things as long as they are happy that everything is going to look like the stencil. Lowering the entry bar for something doesn’t mean you get more qualified people, it means you get more subpar work. BUT, we DO need more COBOL developers, perhaps try teaching that.

    • June 7, 2022 at 4:00 pm
      Permalink

      How do I make first person 3D Player/Camera movement? I tried for so long yet it never worked. can you help me with that please?

    Leave a Reply

    Your email address will not be published. Required fields are marked *