rksoftware

Visual Studio とか C# とかが好きです

C# 9.0 の確認「最上位レベルのステートメント」

C# 9.0 の確認の目次はこちら

■ 最上位レベルのステートメント

ドキュメントはこちら

C# はプロジェクト内のどれかのクラスの static void Main(string[] args) メソッドから実行されるものでしたが、クラスを書かずにいきなりコードが書けるようになりました。

.cs ファイルに

using System;

Console.WriteLine("Hello World!");

と書くだけで実行できます。

これまでは

using System;

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

という感じでした。だいぶ短くなりましたね。

■ 実際に作られるもの

リフレクションでアセンブリの中を観てみましょう。

using System;
using System.Linq;

var fullnames = System.Reflection.Assembly.GetExecutingAssembly().DefinedTypes.Select(a => a.FullName).ToArray();
foreach (var fullname in fullnames)
{
    System.Console.WriteLine(fullname);
    System.Console.WriteLine(Type.GetType(fullname).Assembly.GetName());
    var methods = Type.GetType(fullname).GetMethods(System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Static);
    foreach (var method in methods)
    {
        System.Console.WriteLine(method.Name);
        foreach(var parameter in method.GetParameters())
            System.Console.WriteLine(parameter.Name);
    }
}

実行結果

<Program>$
ConsoleApp1, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null
<Main>$
args
<Program>$+<>c
ConsoleApp1, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null

<Program>$ というクラスに、<Main>$ というメソッドいて、args という引数をとるらしいですね。

■ 引数の仕様

前述のドキュメントにも書いてありますし、先の確認でもわかりましたが、args 引数が使えます。

System.Console.WriteLine(string.Join(", ", args));

実行結果

>ConsoleApp1.exe abc def ghi
abc, def, ghi

確かに使えています。

■ クラスも普通に

ドキュメントにも書いてありますが、クラスなども普通に使えます。

var saitama = new Saitama();
saitama.Say();
saitama.Say();

class Saitama
{
    public void Say() => System.Console.WriteLine("Saitama!"); 
}

実行結果

Saitama!
Saitama!

後方に記述したクラスも使えて普通に使えます。

■ 名前空間

最上位レベルのステートメントは名前空間と組み合わせられないようです。

namespace Startup
{
    //new ConsoleApp1.Saitama().Say();    // ここはエラーになる。
}

// 以降はエラーにならない
namespace ConsoleApp1
{
    class Saitama
    {
        public void Say() => System.Console.WriteLine("Saitama!");
    }
}

↓ は OK

new ConsoleApp1.Saitama().Say();

namespace ConsoleApp1
{
    class Saitama
    {
        public void Say() => System.Console.WriteLine("Saitama!");
    }
}

最上位レベルのステートメントの書かれたファイルの中でクラスなどを書く際には問題なく名前空間が書けます。

■ 二つの最上位レベルのステートメント

ドキュメントにも書いてありますが、最上位レベルのステートメントはプロジェクトに一つだけです。
最上位レベルのステートメントの書き方のファイルが複数ある場合エラーになります。

  • Program.cs
System.Console.WriteLine("Saitama!");
  • Program2.cs
//System.Console.WriteLine("Saitama!");    // 二つ目のファイルはエラーになって書けない

■ Startup object の選択

最上位レベルのステートメントが存在するプロジェクトでプロジェクトのプロパティでスタートアップ オブジェクトの選択は可能ですが、ビルドエラーになります。
f:id:rksoftware:20201122201211j:plain

System.Console.WriteLine("Saitama!");

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

スタートアップ オブジェクトの選択に該当するクラス/メソッドがあっても、そちらが使われるわけではなくビルドエラーです。

■ Startup object を選択しない場合

Startup object を選択していない場合、( (Not set) の場合 ) は最上位レベルのステートメントが選ばれるようです。

System.Console.WriteLine("Saitama!");

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

実行結果

Saitama!

■ まとめ

サンプルなどでは今後は積極に使われると思います。
ドキュメントにある Azure Functions もこちらが基本になるのでしょう。自分が書かなくても読む機会はかなり多いと思います。備えましょう。