rksoftware

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

C# 2.0 以降の新機能の確認 - C# 2.0 - ジェネリック

C# 2.0 以降の新機能を一つづつ確認していきます。
以前に一度行ったのですが、公式ドキュメント再編でリンク切れしているところを見つけてしまったので。今ならもっと簡潔なサンプルが欠けるところもあるだろうし、せっかくなので今もう一度確認して行きます。

ジェネリック

 https://docs.microsoft.com/ja-jp/dotnet/csharp/programming-guide/generics/
 List<T> クラスなどの T の機能。List<int> とすると int 型しか格納できない型安全な List を使用できる。

List<int> ints = new List<int>();
// ints.Add("text"); <- これはできない
ints.Add(1);
List<string> strings = new List<string>();
// strings.Add(1); <- これはできない
strings.Add("text");

Visual Studio の 16.8.4 がリリースされました

Visual Studio のアップデート 16.8.4 がリリースされました。

今回も脆弱性の対応も含まれています。素早くアップデートしましょう。

■ 更新内容

今回も複数の問題がありますが、いくつかは結構大問題もあります。ただ今回はよくわからないかったので手元の環境で確認してみたら再現はしなかった件が多いです。
わかる範囲で見ていきましょう。

問題の解決

次の問題が対策されました。

  • これは誰もが踏んでいるはず。以前のバージョンで既に IntelliCode がインストールされていた場合に、C# の IntelliCode コンポーネントがインストールされない問題があったそうです。
  • PackageReference 形式のプロジェクトが packages.config プロジェクトに依存関係がある時にプロジェクト参照の解決問題があったそうです。
  • x86_64 watchOS 7.0+ シミュレータに問題があったようです。
  • コード分析で警告にしているものがエラーとして扱われてしまうことがあったそうです。
  • ClickOnce でインストールした場合に NuGet でインストールしたパッケージの参照に問題がありアプリケーションが起動できないことがある(パッケージによる)。
  • C++ で_variant_t を使った際に無用に C33005 警告になる問題があったそうです。
  • Git でマージ後に即コミットしない設定にしているにもかかわらず即コミットされてしまうことがあったそうです。
  • .NET 5 プロジェクトの Windows 向けアプリで Tasks と Parallel Stacks 並列スタックの表示が空になったりエラーになったりすることがあったそうです。
  • SQL のスキーマ比較でファイルは変更されるものの保存はされない (ファイル名に * がついている状態) 問題 (仕様?) があったそうです。
  • データベース プロジェクトでファイルを開いていて、その開いているファイルにソリューション エクスプローラーからファイルをドロップすると、開いているファイルにドロップしたファイルのパスが書かれドロップしたファイルが削除されてしまう問題があったそうです。
  • Intellicode でモデルのトレーニングが失敗してしまい行えないことがあったそうです。
  • LiveShare に失敗する問題があったそうです。

機能追加

  • Xcode 12.3 対応

なるべく早くアップデート

今回は誰もが踏む可能性のある問題が対策されています。脆弱性も対策されています。素早くアップデートしておきましょう。

■ 更新方法

Visual Studio の更新はメニューの ツール > ツールと機能を取得 で開くインストーラーから行えます。

きのこ vs たけのこ ついに決着! その対立の要因とは?

日本を二分する山の者と里の者はもはや争う理由なども誰も知らぬ争いを続けていた。

なぜ人は山の者と里の者に分かれるのか? しかもどちらも自分達のほうが優れていると疑わない。そこには、同じ属性の人は集まりやすくいという要因があるのではないか?
つまり、山の者、里の者、それぞれになりやすい属性がいくつかあるのではないか? それがわかれば争いを収められるのではないか? 今回は世界平和に向けその属性の一つを探る壮大な実験の話である。

■ アンケート

結果はかなりはっきりと出ました。
しかし今回わかったこととして、若者側の選択肢を選ぶおっさんが多く世の中は偽りに満ちているということ。
インターネット恐い。

C# 9.0 の確認「パターン マッチングの拡張機能」

C# 9.0 の確認の目次はこちら

■ パターン マッチングの拡張機能

ドキュメントはこちら

C# 9 には、新しいパターン マッチングの機能強化が 」 から始まるブロックです。
皆さん大好きなパターンマッチングが強化されました。これは使いこなしたいですね。

■ コード

■ 条件一つ

この強化によってこんなことが書けるようになりました。

int a = new Random().Next();
if(a is > 1)

これまでの書き方だと

int a = new Random().Next();
if (a > 1)

こんな感じです。これだけ見ると is が増えただけでうれしくは感じないかもしれません。本領はこの先です。

■ 条件二つ

条件として上限値と下限値を設定したい場合こんなことが書けます。

int a = new Random().Next();
if (a is > 1 and < 3)

これまでの書き方だと

int a = new Random().Next();
if (a > 1 && a < 3)

こんな感じです。変数 a を 2 回書くの、すごく嫌じゃないですか。私は嫌です。でもこれからはもう a を書くのは 1 回です。

■ かっこと or

かっこと or も使えます。

int a = new Random().Next();
if (a is (> 1 and < 3) or (> 100 and < 200))

こんな感じです。

■ not

否定の not も使えます。

int a = new Random().Next();
if (a is not (> 1 and < 3))

null チェックに使うのがお勧めのようですね。

string a = null;
if(a is not null)

■ switch で使う

switch 式で使うとこんな感じです。

string Method(object arg)
{
    var text = arg switch
    {
        > 1 => "> 1",
        "a" => "a",
        not null => "not null",
        _ => "null",
    };
    return text;
}

■ まとめ

この機能はとても使いやすいし、使うほどバグも減っていく素晴らしい機能ですね。完全に理解して使いこなしたいですね。

C# 9.0 の確認「localsinit フラグの出力を抑制する」

C# 9.0 の確認の目次はこちら

■ localsinit フラグの出力を抑制する

ドキュメントはこちら

パフォーマンスと相互運用 という項目の 4 つめの塊の 「最後に、」 から始まるブロックです。
この属性がついている場合、ゼロ初期化がなされずパフォーマンスが向上する場合があるそうです。

■ コード

unsafe が許可されている場合のみ使えます(unsafe の中に書く必要はありません)。
例えばこんなコードです。

[System.Runtime.CompilerServices.SkipLocalsInit]
unsafe static void Main(string[] args)
{
    int i;
    int* j = &i;
    Console.WriteLine(*j);
}

この属性は モジュール, クラス, 構造体, コンストラクター, メソッド, プロパティ、インデクサー, イベント, インターフェイス でのみ有効だそうです。例えばフィールドにつけようとすると次のエラーになります。

エラー    CS0592  属性 'System.Runtime.CompilerServices.SkipLocalsInit' はこの宣言型では無効です。'モジュール, クラス, 構造体, コンストラクター, メソッド, プロパティ、インデクサー, イベント, インターフェイス' 宣言でのみ有効です。

■ まとめ

ドキュメントに特に stackalloc 使用する場合、パフォーマンスが大きく向上するとあります。stackalloc を使う場合、それはパフォーマンスが求められる場合であることが多いでしょう。必要が出たときにこの機能を思い出せるように備えましょう。

C# 9.0 の確認「関数ポインター」

C# 9.0 の確認の目次はこちら

■ 関数ポインター

ドキュメントはこちら

パフォーマンスと相互運用 という項目の 3 つめの塊の 「関数ポインターでは、」 から始まるブロックです。
関数ポインターです。ポインターというからには unsafe で書く必要があります。

■ 検証コード

例えばこんなコードです。delegate* が関数ポインターの宣言ですね。そして、関数のポインターを取得しているところが &

class Program
{
    static void Main(string[] args)
    {
        unsafe
        {
            delegate*<string, bool> f = &string.IsNullOrEmpty;
            Console.WriteLine(f(""));
            Console.WriteLine(f("a"));
        }
    }
}

C# のバージョンでは delegate*<string, bool> 次のエラーになります。

エラー    CS8400  機能 '関数ポインター' は C# 8.0 では使用できません。言語バージョン 9.0 以上を使用してください。

■ 実行結果

True
False

見事! string.IsNullOrEmpty(string) が実行されています。

■ まとめ

unsafe コードということで書かない人はまったく書かないと思います。私も普段まったく書きません。
しかし、必要ならば書くことになりますし、備えます。構文簡単ですし。

C# 9.0 の確認「foreach ループの拡張機能 GetEnumerator サポート」

C# 9.0 の確認の目次はこちら

■ foreach ループの拡張機能 GetEnumerator サポート

ドキュメントはこちら

適合性と完成度の機能 という項目の結構読み進めたところにある 「また、foreach ループによって」 から始まるブロックです。
簡単に言うと拡張メソッドで GetEnumerator メソッドを作ればどんな型のオブジェクトでも foreach できます。

■ 検証コード

例えばこんなデータクラスがあったとします。

class Data
{
    public string MyProperty1 { get; set; }
    public string MyProperty2 { get; set; }
    public string MyProperty3 { get; set; }
}

1、2、3 の 3 つのプロパティを持っています。こういったクラスの場合、プロパティを一つずつ上から順に処理をしたい、そう思うことが割とよくあると思います。
そして順に処理するといえば我らが foreach です。しかし、次のようなコードは当然ながらエラーになってしまいます。

class Program
{
    static void Main(string[] args)
    {
        var data = new Data
        {
            MyProperty1 = "Saitama"
            , MyProperty2 = "Chiba"
            , MyProperty3 = "Ibaraki"
        };

        foreach (string datum in data)
            Console.WriteLine(datum);

        Console.WriteLine("Hello World!");
    }
}

class Data
{
    public string MyProperty1 { get; set; }
    public string MyProperty2 { get; set; }
    public string MyProperty3 { get; set; }
}

■ エラーメッセージ

エラー    CS1579  'Data' は 'GetEnumerator' のパブリック インスタンスまたは拡張機能の定義を含んでいないため、型 'Data' の変数に対して foreach ステートメントを使用することはできません

当然の結果ですね。

■ 拡張メソッド GetEnumerator

そこで C# 9 環境で拡張メソッドを作って試してみます。

※検証用コードです。ベストプラクティスをご提案するものではありません。それっぽいコード書いてみたら無駄に長くて焦点がぼやけたので、手抜き実装です。

static class DataExtensions
{
    internal static System.Collections.IEnumerator GetEnumerator(this Data data)
        =>
        new string[]
        {
            data?.MyProperty1
            , data?.MyProperty2
            , data?.MyProperty3
        }
        .GetEnumerator();
}

■ 検証コード

コード全体はこんな感じになりました。

class Program
{
    static void Main(string[] args)
    {
        var data = new Data
        {
            MyProperty1 = "Saitama"
            , MyProperty2 = "Chiba"
            , MyProperty3 = "Ibaraki"
        };

        foreach (string datum in data)
            Console.WriteLine(datum);

        Console.WriteLine("Hello World!");
    }
}

class Data
{
    public string MyProperty1 { get; set; }
    public string MyProperty2 { get; set; }
    public string MyProperty3 { get; set; }
}

static class DataExtensions
{
    internal static System.Collections.IEnumerator GetEnumerator(this Data data)
        =>
        new string[]
        {
            data?.MyProperty1
            , data?.MyProperty2
            , data?.MyProperty3
        }
        .GetEnumerator();
}

■ 実行結果

Saitama
Chiba
Ibaraki
Hello World!

計画通り! プロパティの値が順に処理されました。

■ まとめ

この機能を知らないままに、この機能を使って書かれたコードに出会うと積みそうです。
間違いなくしっかり覚えておきましょう。便利な機能なので流行ると思います。備えましょう。