Search code examples
c#c#-9.0toplevel-statement

Under what conditions will the generated Top Level Statement class be $Program?


I've always been under the assumption that the class generated by Top Level Statements was a hidden, inaccessible class. For example:

System.Console.WriteLine(2);

partial class Program
{
    public static string abc = "def";
}

When ran in SharpLab.io using the default branch or the "C# 9: Top-level statements (27 May 2020)" branch, the generated C# will be

// [ ... using and assembly attributes ... ]
internal static class $Program
{
    private static void $Main(string[] args)
    {
        Console.WriteLine(2);
    }
}
internal class Program
{
    public static string abc = "def";
}

Though it's interesting to note that the default branch emits <Program>$ not $Program and <Main>$ not $Main.

However, it's known you can use a partial class Program to augment the generated class. Modifying the code to print the field...

System.Console.WriteLine(abc);
partial class Program { public static string abc = "def"; }

... and running again with some particular newer branch "C# Next: File types (5 Jul 2022)" will generate the combined class:

// [ ... using and assembly attributes ... ]
internal class Program
{
    public static string abc = "def";

    private static void <Main>$(string[] args)
    {
        Console.WriteLine(abc);
    }
}

For the record, I do not know what C# compiler version is used with this branch, I'm assuming some version of C# 10.

However, the default branch or "C# 9: Top-level statements (27 May 2020)" branch will produce a syntax error, where the abc field cannot be found.

I'm having problems reproducing this issue outside of Sharplab. Creating a .NET 5 / C# 9 (the version that this feature came out in) console application locally compiles and runs:

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <OutputType>Exe</OutputType> <TargetFramework>net5.0</TargetFramework> <LangVersion>9.0</LangVersion>
  </PropertyGroup>
</Project>
System.Console.WriteLine(abc);
partial class Program { public static string abc = "def"; }
// I have SDK 5.0.408 and runtime 5.0.14,15,17 installed, along with others for core 3.1 and .NET 6

Dotnet fiddle will also compile the code, but it only allows me to choose .NET 6, and the C# version is unknown.

Assuming this isn't a bug with Sharplabs, my assumption would be that the class emitted changed for different C# 9 compilers. Sharplabs might be showing the original C# 9 compiler, but my computer might be running a newer version.

The only other evidence I can see is an edit in the dotnet/csharplang git repo that changed the documented emitted class from static class $Program to partial class Program. Even though this file is in the "proposal" folder, it does seem to be the same file that is published to Microsoft's documentation, so at some point in the past it was documented that the class name was $Program.

Why does Sharplab compile the first code block into two classes, and why can't it accept a partial class to augment the entry point class? Was this feature modified after release?


Solution

  • This is now reflected in the Top-level statements feature spec:

    The type is named "Program", so can be referenced by name from source code. It is a partial type, so a type named "Program" in source code must also be declared as partial.

    Which was changed from the previous via this commit on Jun 22, 2022:

    Note that the names "Program" and "Main" are used only for illustrations purposes, actual names used by compiler are implementation dependent and neither the type, nor the method can be referenced by name from source code.

    My guess is that the change was done to support integration testing in ASP.NET Core with the new hosting model in .NET 6 and the first mention of this possibility there is dated Oct 21, 2021.

    So in the nutshell - any modern .NET 6+ compiler should generate Program class, for earlier versions you should not rely on the naming (though .NET 5 is EOL now and this is not very relevant).