どのバージョンのC#としてビルドされているかを調べるC#コード

# これは何？

ビルドエラーからC#のバージョンがいくつとして認識されているのかを調べるコードがあると便利かもしれないと思い作ってみた。

例えばどこかのサイトのオンライン実行環境がどのバージョンのC#までを認識してくれるのかを調べたいとか、そういう時に使えるかもしれない。

```csharp
namespace System.Runtime.CompilerServices
{
    public class CSharp3
    {
        public int AutoImplementedProperty { get; set; }
    }

    public class CSharp4
    {
        public void NamedArguments(int x)
        {
            if (x > 0) NamedArguments(x: x - 1);
        }
    }

    public class CSharp5 : INotifyCompletion
    {
        public static async void AsyncMethod()
        {
            await new CSharp5();
        }

        public CSharp5 GetAwaiter() { return this; }
        public bool IsCompleted { get { return false; } }
        public void OnCompleted(Action action) { }
        public void GetResult() { }
    }
#if NET5_0_OR_GREATER
#else
    interface INotifyCompletion
    {
        void OnCompleted(Action action);
    }

    interface ICriticalNotifyCompletion
    {

    }

    struct AsyncVoidMethodBuilder
    {
        public static AsyncVoidMethodBuilder Create() { return new AsyncVoidMethodBuilder(); }
        public void SetException(Exception exception) { }
        public void SetResult() { }
        public void AwaitOnCompleted<TAwaiter, TStateMachine>(ref TAwaiter awaiter, ref TStateMachine stateMachine) where TAwaiter : INotifyCompletion where TStateMachine : IAsyncStateMachine { }
        public void AwaitUnsafeOnCompleted<TAwaiter, TStateMachine>(ref TAwaiter awaiter, ref TStateMachine stateMachine) where TAwaiter : ICriticalNotifyCompletion where TStateMachine : IAsyncStateMachine { }
        public void Start<TStateMachine>(ref TStateMachine stateMachine) where TStateMachine : IAsyncStateMachine { }
        public void SetStateMachine(IAsyncStateMachine stateMachine) { }
    }

    interface IAsyncStateMachine
    {
        void MoveNext();
        void SetStateMachine(IAsyncStateMachine stateMachine);
    }
#endif

    public class CSharp6
    {
        public string StringInterpolation = $"";
    }

    public class CSharp7
    {
        public static bool PatternMatching(int x)
        {
            return x is 7;
        }
    }

    public class CSharp7_1
    {
        public int DefaultLiteral = default;
    }

    public class CSharp7_2
    {
        public int LeadingUnderscores = 0x_7_2;
    }

    public class CSharp7_3<EnumConstraints>
        where EnumConstraints : Enum
    {
    }

    public class CSharp8
    {
        public static int SwitchExpression()
        {
            return DateTime.Today.Day switch
            {
                _ => 0
            };
        }
    }

    public class CSharp9
    {
        public int TargetTypeNewExpression = new();
    }

    public class CSharp10
    {
        const string ConstantInterpolatedStrings = $"{nameof(CSharp10)}";
    }

    public class CSharp11
    {
        public string RawStringLiteral = """

            """;
    }
}
```

対象のC#バージョンは3.0以上。ターゲットフレームワークは3.5以上で動作する。

出来る限りフレームワークに依存しない機能で判定したかったけど、C#5.0の判定が難しかった（もっと簡単な方法があるかもしれない）。

# 小話

## C#4.0

最初は `dynamic` の利用可否で調べようと思ったが、属性が必要になるので名前付き引数にした。省略可能引数も属性が必要になるからと思っていたが、よくよく考えたら `System.Runtime.InteropServices.OptionalAttribute` クラスは .NET Framework 1.1 から存在していた。

## C#5.0

非同期メソッドは色々な型やメンバーの存在を要求されてなかなかにツラかった。しかし Caller Info や foreach の挙動変更は実行時にしかわからないのでビルドで判断させるにはこれしか思いつかなかった。

## C#6.0

細かい構文の追加がメインのバージョンなので色々と判断基準は作れそう。`nameof` とか。

## C#7.0

存在感のある他の機能だとタプルがあるが、`System.ValueTuple` が必要なので定数パターンによる判定とした。

## C#10.0

`record struct` とか他にもあるだろと言われそうだが、定数の文字列補間で判断させてみた。他のバージョンの構文（文字列補間）に依存した構文はあまり使いたくなかったので、気が向いたら他の機能を使った判定に書きなおすかもしれない。

# まとめ

最新のC#バリバリ書きたい()