sh1’s diary

プログラミング、読んだ本、資格試験、ゲームとか私を記録するところ

Visual Studio 2019 v16.7 アップデートと使いそうな機能まとめ

Visual Studio 2019 のバージョンが正式に v16.7 になりました。プレビューを2つ挟んでのアップデートでしたね。

f:id:shikaku_sh:20200807145142p:plain:w500
v16.7 のアップデート

(個人的に)気になっていた機能をいくつか記録しておきます。(詳細は、最後の参考などから確認いただければと思います)

C# のコーディング変更点

1. デバッガ―ディスプレイのショートカットの追加

Ctrl+.

詳細はこれ

テストコードを書くのもいいけど、実行中のデバッグは、並列処理だと困ることもあるけど、やっぱり強力なのでデバッグしやすくしておく。


2. コード補完機能で日付の書式指定の候補がでる

f:id:shikaku_sh:20200807145237p:plain:w500
従来の書き方も OK

f:id:shikaku_sh:20200807145259p:plain:w500
最近の書き方もフォロー

見たまま。書式指定はかなり忘れっぽかったので、わりと真面目にたすかります。

var time = DateTime.Now;

Console.WriteLine($"{time:yyyy-MM-dd}");
Console.WriteLine(time.ToString("yyyy-MM-dd"));


3. Top level statements

これはとても短いコードを書くとき用の機能で、Main メソッドを書かなくてもコンパイルできる仕組みのようです。特殊な予約語として args で引数にもアクセスできる。(内部的には生成するメソッドが切り替わる)

using system;

Console.WriteLine("Top level statements");

[STAThread] のような属性を付与するときは、Main が必要なったりするなど、この機能は、あくまでも簡単なコードを書くためのものとしたほうがいいです。あれこれ求める機能では、そもそもなさそうです。


3.1 Top level statements と Main メソッドがあるときの動作

Top level statements が優先されるみたいです。

f:id:shikaku_sh:20200807145858p:plain:w600
これこそ普通やらない


4. record 型の追加

record は class や struct に対する修飾子(abstract, sealed, static など)とは違って、新しい型です。(最初は data の修飾子があったようだけど)delegate のようにコード中に浮いたような感じのコードに見えるのですが、以下のとおり。

record Sample(int x, int y);

var sample = new Sample(1, 2);

Console.WriteLine($"{sample.x}, {sample.y}");

内部的に init アクセサーを利用するようです。


4.1 record のリフレクション

基本的には、書き換え不可の record ですがリフレクションを使うと setter を取得して書き換えできてしまいます。(良い悪いの話では無いおまけ的な話)

public record Record { public int X { get; init; } }

var record = new Record(1);

var prop = typeof(Record).GetProperty("X");
var setter = prop.GetSetMethod();

setter.Invoke(record, new object[] {2});
Console.WriteLine(record.X); // output 2


4.2 With 式

record 型の with 式を使った呼び出しは、ちょっと特殊なケースになりそうですが、これは non-destructive mutation(非破壊的な変更)にあたるものだと思います。

var otherSill = personSill with { name = "シィル・プライン (IP)" };

with 式は値の変わらないデータを、既知の値から一部だけ変更して新しいデータを作成するために利用します。なので、with 式を伴った record は、その人のある時点を表現しているのような形になりそうです。


5. init アクセサー (init accessor)

get/set のアクセサーのうち、set アクセサーを init にスイッチできます。(set and init を同時に実装できない)

class Sample
{
    public int Data { get; init; }
}

プロパティの値を書き換えるために set アクセサーをつけますが、クラス内だけアクセスがあるときは private set; にしていたと思います。

そこから、名前のとおり初期化のタイミングだけアクセスを許可するときのために init が追加された形です。ただ、正確には制限付きの set アクセサーのようなものなので、次の選択から呼び出せるようです。

Init アクセサーが出てきたために Init という単語は、暗黙的な初期化の意味が強まった気がしています。私は初期化メソッドの名前を Initialize にして省略せず書いていたのですが、基本的には初期化のタイミングでだけ呼び出すということなら、Init のほうが伝わりやすくなったのかもしれないです。

現行の注意として、init を利用するためには、次のコードを追加しないと正しく動きませんでした。見ての通り init を追加する拡張ですね。

// .NET5 で BCL に追加予定。.NET5 でなくても独自に定義することができる。
namespace System.Runtime.CompilerServices
{
    internal class IsExternalInit : Attribute { }
}


5.1 init の詳しいところ

実際のところ、init アクセサーは record, class, struct のどの型でも追加することができます。(岩永さんの youtube 参考

public record Record
{
    public int X { get; init; }
}

public class Class
{
    public int X { get; init; }
}

public struct Struct
{
    public int X { get; init; }
}

こうなると、record 型を使わなくても init でいいじゃないか、となるのですが、実際は型が持っている Equals メソッドの動作が異なるようです。

class はインスタンスの一致 (reference equal) を見ますが、record と struct はメンバーの値の一致を見ます。(memberwise equal)

var r1 = new Record{ X = 1 };
var r2 = new Record{ X = 1 };
var c1 = new Class{ X = 1 };
var c2 = new Class{ X = 2 };

Console.WriteLine(r1.Equals(r2)); // True
Console.WriteLine(c1.Equals(c2)); // False

Console.WriteLine(r1 == r2); // reference equal False

現状は、struct とrecord は同じような動きをしますが、内部的に struct は Runtime の特別な対応でメンバーの一致を調べているので、record と比べて動作が遅いようです。

record と struct はかなり似ているもので、ちょっと class な struct が record くらいの位置になっています。

また、init は IL 上ではなにもしていないので、ただの set です。C# Compiler の段階で処理する機能になっているので、実行時の差は考慮する必要がなさそうです。


6. is 演算子の警告

以下のコードで x is not の形を(間違って)表すことが多かったためみたいです。

実際は C# 8.0 で追加された x! (null-forgiving) になってしまうので、is 演算子と別のオペレーターとして処理されてしまうようです。(しかも is 演算子は null チェックを含むので無意味という)

public void Sample(object? o)
{
    if (o !is 1)
    {
        Console.WriteLine("o is 1");
    }
    throw new Exception("o is not 1");
}

f:id:shikaku_sh:20200807145403p:plain:w600
たしかに勘違いするかもしれない

warning IDE0080: Suppression operator has no effect and can be misinterpreted.
サプレッション演算子 (!) は効果がなく、誤解される可能性があります。


XAML の変更点

1. カラーが見えるようになった

これは見ての通り、すぐ気づいた。えらい。

f:id:shikaku_sh:20200807145525p:plain:w500
えらい

2. XAML のリロードボタンが追加された

これは、以下のショートカットキーがボタンになって追加されたようなものかな。XAML がエラーでクラッシュしたときとかに使えるかも。


参考

Effective C# 6.0/7.0

Effective C# 6.0/7.0