C# 9.0 is finally here, exploring two new features of Top-level programs and Partial Methods

Posted Jun 27, 20204 min read

One:background

1. Storytelling

.NET 5 finally released the sixth preview version on June 25, and with it came more new features added to the C# 9 Preview, this series can also continue to write down, without further ado, Today, take a look at the two new features of Top-level programs and Extending Partial Methods.

2. Required for installation

Download the latest .net 5 preview 6.

Download the latest Visual Studio 2019 version 16.7 Preview 3.1

Two:research on new features

1. Top-level programs

If you have played python, you should know that writing a print in xxx.py, this program can run, simple and efficient and rude, I am very happy that this feature was brought to C# 9.0.

  • before fixing
using System;

namespace ConsoleApp2
{
    class Program
    {
        static void Main(string[]args)
        {
            Console.WriteLine("Hello World!");
        }
    }
  • After modification
System.Console.WriteLine("Hello World!");

This is interesting. Where did the Main entry function go? Without it, how does JIT compile the code? If you want to know the answer, just use ILSpy decompilation and take a look!

.class private auto ansi abstract sealed beforefieldinit $Program
    extends [System.Runtime]System.Object
{
    .custom instance void [System.Runtime]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() =(
        01 00 00 00
   )
    //Methods
    .method private hidebysig static
        void $Main(
            string[]args
       ) cil managed
    {
        //Method begins at RVA 0x2050
        //Code size 18(0x12)
        .maxstack 8
        .entrypoint

        IL_0000:ldstr "Hello World!"
        IL_0005:call void [System.Console]System.Console::WriteLine(string)
        IL_000a:nop
        IL_000b:call string [System.Console]System.Console::ReadLine()
        IL_0010:pop
        IL_0011:ret
    } //end of method $Program::$Main

} //end of class $Program

From the perspective of IL, the class becomes $Program and the entry method becomes $Main, which is fun. In our impression, the entry function must be Main, otherwise the compiler will give you a big Error, you added a $symbol, can the CLR recognize it? Can you recognize that we use windbg to look at some managed and unmanaged stacks and see what new discoveries.

0:010> ~0s
ntdll!NtReadFile+0x14:
00007ffe`f8f8aa64 c3 ret
0:000> !dumpstack
OS Thread Id:0x7278(0)
Current frame:ntdll!NtReadFile + 0x14
Child-SP RetAddr Caller, Callee
0000008551F7E810 00007ffed1e841dc(MethodDesc 00007ffe4020d500 + 0x1c System.Console.ReadLine()), calling 00007ffe400ab090
0000008551F7E840 00007ffe4014244a(MethodDesc 00007ffe401e58f0 + 0x3a $Program.$Main(System.String[])), calling 00007ffe40240f58
0000008551F7E880 00007ffe9fcc8b43 coreclr!CallDescrWorkerInternal + 0x83 [F:\workspace\_work\1\s\src\coreclr\src\vm\amd64\CallDescrWorkerAMD64.asm:101]
0000008551F7E8C0 00007ffe9fbd1e03 coreclr!MethodDescCallSite::CallTargetWorker + 0x263 [F:\workspace\_work\1\s\src\coreclr\src\vm\callhelpers.cpp:554], calling coreclr!CallDescrWorkerWithHandler [F:\workspace\_work1 \s\src\coreclr\src\vm\callhelpers.cpp:56]
0000008551F7E950 00007ffe9fb8c4e5 coreclr!MethodDesc::IsVoid + 0x21 [F:\workspace\_work\1\s\src\coreclr\src\vm\method.cpp:1098], calling coreclr!MetaSig::IsReturnTypeVoid [F:\workspace\ _work\1\s\src\coreclr\src\vm\siginfo.cpp:5189]
0000008551F7EA00 00007ffe9fb8c4bf coreclr!RunMainInternal + 0x11f [F:\workspace\_work\1\s\src\coreclr\src\vm\assembly.cpp:1488], calling coreclr!MethodDescCallSite::CallTargetWorker [F:\workspace\_work\1 \s\src\coreclr\src\vm\callhelpers.cpp:266]
0000008551F7EB30 00007ffe9fb8c30a coreclr!RunMain + 0xd2 [F:\workspace\_work\1\s\src\coreclr\src\vm\assembly.cpp:1559], calling coreclr!RunMainInternal [F:\workspace\_work\1\s\ src\coreclr\src\vm\assembly.cpp:1459]

From the flow chart of the stack above:coreclr!RunMain -> coreclr!MethodDesc -> coreclr!CallDescrWorkerInternal -> $Program.$Main, was indeed called, but there is a major discovery, in $Program.$Main reads the method descriptor before calling the underlying CLR. This is a major breakthrough. Where is the method descriptor? You can use ildasm to look at the metadata list.

It can be seen that the entry function is marked with an ENTRYPOINT tag, which means that the entry function name can actually be changed at will. As long as it is marked by ENTRYPOINT`, CoreCLR can recognize it~

2. Partial Methods

We know that some methods are a good stub function, and they have been implemented in C# 3.0. At that time, we added a lot of restrictions, as shown below:

Translated is:

  • The signatures of some methods must be consistent
  • The method must return void
  • Access modifiers are not allowed and are implicitly private.

In C# 9.0, all restrictions on method signatures are released, as the issue summarizes:

This is very good news. Now you can add various types of return values to some of your methods. Here is an example:

    class Program
    {
        static void Main(string[]args)
        {
            var person = new Person();

            Console.WriteLine(person.Run("jack"));
        }
    }

    public partial class Person
    {
        public partial string Run(string name);
    }

    public partial class Person
    {
        public partial string Run(string name) => $"{name}:open slip~";
    }

Then we use ILSpy to simply look at how to play the bottom layer. The following figure shows that it is actually a simple composition, right?

Now that I have an idea, what happens if I don't implement the Run method? Comment out the partial class below and take a look.

From the error message, the accessible modifier must have a method to achieve, thought it was erased during direct compilation. This does not play the role of a pile function:-D, but this feature still gives us more possible application scenarios.

Three:summary

The two features in this article are still very practical. Top-level programs allow us to write less code, and even pick up the notepad to quickly write test code similar to one-time use. Partial Methods feature is left to you to add, I basically haven't used it(__).