目次

キーワード

Ver. 8.0

随時更新。

予定: https://github.com/ufcpp/UfcppSample/issues/208

  • Visual Studio 2019 のリリース時点では、C# 8.0 はプレビュー版という扱いになります
    • 予定している機能も全ては実装されておらず、実装されている機能も自己責任での利用をお願いします
  • プレビューでもいいので C# 8.0 を使いたい場合、言語バージョン8.0preview を指定
  • プレビューとはいえ割かし安定して使えそうなものについては以前書いたブログを参照

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

C# 8.0 (.NET Core 3.0)で、インターフェイスの制限が緩和されました。 以下のようになります。

これら指して「インターフェイスのデフォルト実装」(default implementations of interfaces)と呼びます。

using System;
 
interface I
{
    void X();
 
    // 後から追加しても、デフォルト実装を持っているので平気
    void Y() { }
}
 
class A : I
{
    // X だけ実装していればとりあえず大丈夫
    public void X() { }
}
 
class B : I
{
    public void X() { }
 
    // Y も実装。I 越しでもちゃんとこれが呼ばれる。
    public void Y() => Console.WriteLine("B");
}
 
class Program
{
    static void Main() => M(new B());
    static void M(I i) => i.Y();
}
B

機能面で言うと、クラス(特に抽象クラス)との差は「フィールドを持てない代わりに多重継承できる」というくらいに縮まりました。 ただ、 既存機能・既存コードへの影響を最小限にとどめるためであったり、 いくつかの理由からクラスの場合と既定動作などに差があるため注意が必要です。

詳しくは「インターフェイスのデフォルト実装」で説明します。

ただし、インターフェイスのデフォルト実装は C# コンパイラー上のトリックだけでは実装できず、 .NET ランタイム側の対応が必要な機能です。 C# 8.0 以降を使っていても、ターゲットとなるランタイム(TargetFramework)が古いと使えません。 詳しくは以前書いたブログ「RuntimeFeature クラス」で説明しています。

再帰パターン

C# 7.0 で部分的にパターン マッチングが実装されていましたが、C# 8.0 で完全版になります。 C# 8.0 で追加されるパターンは再帰的なマッチングが可能で、「再帰パターン」(recursive pattern)と呼ばれたりもします。

例えば以下のような感じです(new! と書いている行が再帰パターン)。

public class Point
{
    public int X { get; set; }
    public int Y { get; set; }
    public Point(int x = 0, int y = 0) => (X, Y) = (x, y);
    public void Deconstruct(out int x, out int y) => (x, y) = (X, Y);
}
 
class Program
{
    static int M(object obj)
        => obj switch
    {
        0 => 1,
        int i => 2,
        Point (1, _) => 4, // new! 位置パターン。
        Point { X: 2, Y: var y } => y, // new! プロパティ パターン。
        _ => 0
    };
}

詳しくは「再帰パターン」で説明します。

switch 式

switchを式として書けるようになりました。 また、従来の switch ステートメントは C# の前身となるC言語のものの名残を強く残し過ぎていて使いにくいものでしたが、その辺りも解消されて使いやすくなりました。

以下のような書き方ができます。

public int Compare(int? x, int? y)
    => (x, y) switch
    {
        (int i, int j) => i.CompareTo(j),
        ({ }, null) => 1,
        (null, { }) => -1,
        (null, null) => 0
    };

後置きの switch キーワードに続けて、{} 内にパターンと返したい値を並べます。

詳しくは「switch」で説明します。

using ステートメントの改善

using 変数宣言

変数宣言に対して using 修飾を付けることで、 その変数のスコープに紐づいて using ステートメントと同じ効果を得られるようになりました。 これを using 変数宣言(using declaration)と呼びます。

using System;
 
readonly struct DeferredMessage : IDisposable
{
    private readonly string _message;
    public DeferredMessage(string message) => _message = message;
    public void Dispose() => Console.WriteLine(_message);
}
 
class Program
{
    static void Main()
    {
        // using var で、変数のスコープに紐づいた using になる。
        // スコープを抜けるときに Dispose が呼ばれる。
        using var a = new DeferredMessage("a");
        using var b = new DeferredMessage("b");
 
        Console.WriteLine("c");
 
        // c, b, a の順でメッセージが表示される
    }
}

詳しくは「using 変数宣言」で説明します。

パターン ベースな using

ref 構造体に限るんですが、 パターン ベース(別にインターフェイスを実装していなくても、Dispose メソッドさえ持っていればOK)で using ステートメントを使えるようになりました。

// ref 構造体なので IDisposable インターフェイスは実装できない。
ref struct RefDisposable
{
    // けど、Dispose メソッドだけ用意。
    public void Dispose() { }
}
 
class Program
{
    static void Main()
    {
        // C# 7.3 まではコンパイル エラーになっていた。
        // C# 8.0 で OK に。
        using (new RefDisposable()) { }
    }
}

ref 構造体だけ対応したのは、需要が高く、既存コードを壊す心配が少ないからです (既存コードの心配さえなければ任意の型で認めたかったそうです)。

詳しくは「パターン ベースな using」で説明します。

その他

こまごまとした修正がいくつかあります。

null 合体代入 (??=)

C# 8.0 では、null合体演算子 (??)も複合代入に使えるようになりました(??=)。

static void M(string s = null)
{
    s ??= "default string";
    Console.WriteLine(s);
}

詳しくは「null 合体代入 (??=)」で説明します。

静的ローカル関数

C# 8.0 から、外部の変数を捕獲しないことを明示するため、 ローカル関数に static 修飾を付けれるようになりました。 この機能を静的ローカル関数(static local function)と呼びます。

void M(int a)
{
    // 外部の変数(引数)を捕獲(クロージャ化)。
    int f(int x) => a * x;
 
    // static を付けて、クロージャ化を禁止。
    // a を使っているところでコンパイル エラーになる。
    static int g(int x) => a * x;
}

詳しくは「静的ローカル関数」で説明します。 同時に、変数のシャドーイングも認められるようになりました。

@$

C# 7.0 では、文字列リテラル""の前に$@と付けることで、複数行に渡る文字列補間ができましたが、$@の順序は$@しか認められていませんでした。

C# 8.0では@$の順でも認められるようになりました。

アンマネージなジェネリック構造体

C# 8.0 では、ジェネックな構造体に対して再帰的にアンマネージ型かどうかの判定するようになりました。 型引数全てがアンマネージであれば、その構造体もアンマネージ扱いを受けるようになります。

using System.Collections.Generic;
 
class Program
{
    unsafe static void Main()
    {
        var kv = new KeyValuePair<int, int>(1, 2);
        KeyValuePair<int, int>* pkv = &kv;
    }
}

詳しくは「アンマネージなジェネリック構造体」で説明します。

readonly 関数メンバー

C# 8.0 で、関数メンバー単位で「フィールドを書き換えてない」ということを保証できるようになりました。

struct Point
{
    public float X;
    public float Y;
 
    // readonly 修飾でフィールドを書き換えないことを明示
    public readonly float LengthSquared => X * X + Y * Y;
}

隠れたコピー」問題を避けやすくなります。

詳しくは「readonly 関数メンバー」で説明します。

ジェネリック型に対する is null

ほぼ「バグ修正」レベルですが、 以下のコードがコンパイルできるようになりました。

static bool M<T>(T x) => x is null;

元々 x == null であればコンパイルできていたのに、x is null がコンパイルできないのは変だということで修正されました。 型引数 T非 null 値型の時には常に false になります。

更新履歴

ブログ