これまで紹介してきたもの以外にも、C# 8.0での導入が予定されている機能はいくつかあります。 ただ、Visual Studio 2019 Preview 1でまだ実装されていない機能・ちゃんと動いていない機能はまとめて軽く紹介して終わりにしようかと思います。 次以降のPreviewで実装されたらまた改めて紹介します。

インターフェイスのデフォルト実装

インターフェイス中のメソッドに実装を持てるようになります。 これに関しては昔書いた記事があるのでそちらを参照:

先日「RuntimeFeature クラス」で紹介した通り、 ランタイムの修正が必須の機能です。

pattern-base な using/foreach

前に1度書いていますが、C# には単なるメソッド呼び出しに置き換えるような、シンタックスシュガーな文法が結構あります。 例えば、クエリ式の場合、以下の2行は全く同じ意味になります。

var q1 =
    from x in source
    where x > 5
    select x * x;
 
var q2 = source
    .Where(x => x > 5)
    .Select(x => x * x);

問題はここから先。 クエリ式の場合は、このWhereSelectメソッドにかなり自由が効きます。

  • 特にインターフェイスの実装等は必要なく、所定のパターンを満たしていれば何でもいい
  • インスタンス メソッドでも拡張メソッドでもいい
  • オプション引数や params があってもいい

一方で、foreachの場合だと以下の制限が掛かります。

  • インスタンス メソッドでないとダメ
  • オプション引数や params があるとダメ

さらに、usingステートメントに至ってはもっと厳しい制限が掛かっています。

  • IDisposableインターフェイスを実装していないとダメ

これに対して、C# 8.0 では、foreachusingでもクエリ式と同程度の緩さで「パターンでの(pattern-based)実装」が認められるようになります。 昨日紹介した非同期版の foreach も同様です。

ちなみに、提案では「enhanced using」と呼ばれていて、 次節の「using declarationとセット」、かつ、「usingの方が主役でforeachの方はおまけ」です。

using declaration

usingステートメントに対して、以下のような要望は多いです。

  • usingのネストがしんどい、
  • Disposeしたいタイミングはほとんどの場合、変数のスコープと同じ

ということで、以下のように、変数に対する修飾子としてusingを書くことで、 その変数のスコープから抜けるときにDisposeを呼ぶという機能を追加する予定です。

struct A
{
    void Dispose() => Console.WriteLine("A Disposed");
}
 
class Program
{
    static void Main()
    {
        using var a = new A();
        using var b = new A();
 
        {
            using var c = new A();
            // c のスコープはここまでなので、ここで c.Dispose()
        }
 
        // ここで b.Dispose(); a.Dispose();
        // ちなみに、宣言とは逆順で呼ばれる
    }
}

Target-typed new

C# 7.1で入ったdefaultと同様に、newに対しても左辺からの型推論が効くようになります。

// これは 右→左 の推論。C# 3.0 の頃から使える。
var a1 = new A(1, 2);
 
// C# 8.0 では、左→右 の推論が入る。
A a2 = new(1, 2);

caller expression attribute

C# 5.0で、Caller Info 属性というものがいくつか入っています。 以下のように、コンパイラーによって呼び出し元のメソッド名などを挿入してもらう機能です。

using System;
using System.Runtime.CompilerServices;
 
class Program
{
    static void M([CallerMemberName]string callerName = null)
        => Console.WriteLine(callerName);
 
    static void Main()
    {
        // M には何も引数を渡していないものの、
        // CallerMemberName が付いているので null ではなく、呼び出し元のメソッド名
        // (この場合は "Main")がコンパイラーによって挿入される。
        M();
    }
}

C# 8.0で、この手の属性が1つ増えます。 CallerArgumentExpression属性を付けることで、 引数に渡した式全体を受け取れます。

using System;
using System.Runtime.CompilerServices;
 
class Program
{
    static void M(int x, [CallerArgumentExpression("x")]string xExpression = null)
        => Console.WriteLine(xExpression);
 
    static void Main()
    {
        M(1 + 2 + 3); // "1 + 2 + 3" が xExpression に渡る
        M(2 * 3);     // 同上、"2 * 3"
    }
}

わかりやすい用途は、例えばXUnit.Assertとかです。 単体テストが失敗したときに、失敗の原因になった式をログに表示できます。

generic attributes

属性にジェネリックなクラスを使えるようになります。

using System;
 
class MyAttribute<T> : Attribute { }
 
[My<int>]
class Target { }

機能一覧

ここで紹介したのは、roslyn リポジトリにあるLanguage Feature Statusを元に選んだものです。 一方で、csharplang の方の 8.0 candidate マイルストーンの方には他にもいくつか並んでいます。

7.0 の時の経験からいうと、 基本的にはLanguage Feature Statusに並んでいるものが実装されていきますが、 多少の入れ替わりはあったりします。 急にLanguage Feature Statusに追加されるものもあれば、 今並んでいても8.xに回されることもあります。

例えば、実装状況を見るに、以下の2つなんかはLanguage Feature Statusに並んでいませんが、8.0 に入るんじゃないかという感じがします。