rksoftware

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

Visual Studio (Win) で C# 7.2 が使えるらしいので試してみた

Visual Studio (Win) で C# 7.2 が使えるらしいので試して見ました。

Visual Studio 2017 15.5 Preview 4 が必要らしいのでインストールします。
今の Visual Studio は同一メジャーバージョン/エディションでも複数インストールできるのではかどりますね。

■ C# 7.2 の設定

プロジェクトを新規に作成したらいつもの手順で、C# 7.2 を使えるように設定します。
プロジェクト > ××のプロパティ > ビルド > (右下の方の)[詳細設定]ボタン > 全般の[言語バージョン] を C# 7.2 に設定 これで、C# 7.2 が使えるはずです。
とりあえず今回は上記のリリースノートに書かれている機能を試してみました。

■ Span を試す

Span を使うには、NuGet で System.Memory を追加します。NuGet で探す際に [プレリリースを含める]チェック を ON にしないと出てこないので注意してください。

var ary = Enumerable.Range(1, 10).ToArray();
var span = new Span<int>(ary, 2, 3);
var readonlySpan = (ReadOnlySpan<int>)span;
for (int i = 0; i < readonlySpan.Length; i++)
    Console.WriteLine(readonlySpan[i]);
// 3
// 4
// 5 と出力される
span[0] = 11;
Console.WriteLine(readonlySpan[0]); // 11 と出力される
// readonlySpan[0] = 21; ← これはエラー
ary[2] = 31;
Console.WriteLine(readonlySpan[0]); // 31 と出力される

Console.ReadKey();

・Span は配列(など)の一部の範囲を切り出して値の参照を持てる
・Sapn を new する際に引数で「2 番目の要素から長さ 3」と指定していますが、for で回した際に確かに「2 番目の要素から 3 つ」が出力された
・最初に作った ary という配列の要素を変更した際に、Span の値も変更されているので確かに参照を持っている
・Span だけでなく ReadOnlySpan という読み取り専用の型も存在する

ちなみにこれ、System.Memory さえ参照すれば、C# のバージョンは関係ないみたいですね。C# 3 に変更しても動作します。

蛇足ですが、Span にある DangerousCreate ってメソッドの名前、惹かれるものがありますよね?

■ readonly struct を試す

readonly struct SA
{
    public readonly int PA;
    // public int PB; ← これはエラー
    public SA(int a)
    {
        PA = a;
    }
    // public void Set(SA a) { this = a; } ← これはエラー
}

struct SB
{
    public readonly int PA;
    public int PB; // ← readonly でなければ OK
    public SB(int a, int b)
    {
        PA = a;
        PB = b;
    }
    public void Set(SB a) { this = a; } // ← readonly でなければ OK
}

・readonly struct は readonly なメンバーしか持てない
・保持する値が変更されない(値が変更可能になる定義が禁止される)

■ in parameters / ref readonly returns を試す

struct SB
{
    public int PA;
    public void Set(int a) => this.PA = a;
}

static ref readonly SB MyMethod(in SB a)
{
    // a.PA = 11; ← 読み取り専用になるため値を変更できない
    // a = new SB(); ←読み取り専用になるため値を変更できない
    Console.WriteLine(a.PA); // 10 と出力される
    a.Set(12);               // ←エラーにはならないが、読み取り専用になるため値を変更できない
    Console.WriteLine(a.PA); // 10 と出力される
    return ref a;
}
static void Main(string[] args)

{
    var sb1 = new SB { PA=10 };
    ref readonly var sb2 = ref MyMethod(sb1);
    Console.WriteLine(sb1.PA); // 10 と出力される
    Console.WriteLine(sb2.PA); // 10 と出力される
    sb1.Set(13);
    Console.WriteLine(sb1.PA); // 13 と出力される
    Console.WriteLine(sb2.PA); // 13 と出力される
    // sb2.PA = 14; //  ←読み取り専用になるため値を変更できない
    sb2.Set(14);    //  ←エラーにはならないが、読み取り専用になるため値を変更できない
    Console.WriteLine(sb1.PA); // 13 と出力される
    Console.WriteLine(sb2.PA); // 13 と出力される
    Console.ReadKey();
}

これはちょっとルールが多くて複雑です。
とりあえずやりたいこととしては
・構造体を参照で渡してパフォーマンスを上げたいが、メソッド内/ return 先で書き換えられたくない場合に使う
・構造体を参照で渡しつつ、読み取り専用に制限できる

■ private protected を試す

次の二つの名前空間は別アセンブリにあるとします。

namespace ClassLibrary1
{
    public class Class1
    {
        private protected void MyMethod() {; }
    }

    public class Class3 : Class1
    {
        void MyMethod2() { base.MyMethod(); } // ← 同一アセンブリ内の派生クラスは OK
    }
}
namespace AnotherAssemblyClass
{
    class AnotherAssemblyClass : ClassLibrary1.Class1
    {
        void MyMethod3()
        {
            // base.MyMethod(); ← これはエラー
        }
    }
}

・同一アセンブリ内でのみアクセスできる

■ Non-trailing named arguments を試す

static void MyMethod(int a, int b, int c, int d)
{
    ;
}

static void Main(string[] args)
{
    MyMethod(1, 2, c:3, 4);         // ← c の位置が順番とあっているので OK
    MyMethod(1, 2, d: 3, c:4);      // ← 名前付きが後方に集まっているので OK
    // MyMethod(1, c: 2, b: 3, 4);  // ← c、b の位置が順番とあっていないので NG
    MyMethod(d:1, c:2, b: 3, a:4);  // ← 全て名前付きなので OK
    // MyMethod(1, c: 2, b: 3, a: 4); // ← a の位置は既に値が書かれている (1) ので NG
}

・位置が順番とあっていれば、末尾でなくても引数に名前を付けられる

その他の 7.2 の機能

とりあえず、リリースノートに書かれている機能は確かに確認できそうです。
その他のリリースノートに書かれていない機能は、また後日試してみます。