sh1’s diary

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

C# init アクセサー (C#9.0) の使い方

C# 9.0 から init アクセサーが追加されています。比較的に使いやすいアクセサーですが、利用しているでしょうか。仕様を把握しているでしょうか。

これまでの流れ

getter/setter で記述するパターンは、(アクセス修飾子を除くと)だいたいこんな感じの流れがありました。

  1. get; set; (C# 3.0)
  2. get; (C# 6.0)
  3. get; init; (C# 9.0)

一番の基本形となるパターン1で戸惑うことは無いけど、2と3の違いを把握できていますか。そこが init アクセサーを追加したポイントになると思います。先に違いの要点を整理すると下図のとおり:

変数 コンストラクター オブジェクト初期化子 メソッド
1.get; set;
2.get; × ×
3.get; init; ×

パターンの1のように、メソッドからアクセスできてしまうと、値変更のアクセスに制限はありません。パターン2は、値変更がコンスタクター内でしかできなかったため、かなり厳しい制限です。つまり、get; init; が必要になった理由は get; のみだと値を初期化する制限がコーディングする上で厳しすぎるという雰囲気になり、ちょうどいい値の初期化制限を実現するために生まれたものです。

アクセス修飾子 (private, protected) は class 外からのアクセス制限をするものなので、目的が異なります。(コンストラクターを private にして、singleton パターンを実装することもあるので、初期化と関係が無いわけでもありませんが)

一番の違いはオブジェクト初期化子

オブジェクト初期子でのデータの書き換えは、下のような場合です。SharpLab でも書いてみました。

var data = new SampleData
{
    Data = 100
};

record 型専用の初期化 with

C# 9.0 から with 式を利用できるようになりました。with 式は、一般的なクラスの値初期化で利用できるキーワードではなくて、データの読み書きに特化した record 型のときに利用できるキーワードです。(C# 10.0 以降は、構造体と匿名型も対象ですが、利用目的は基本同じ)

using System;

public class Program
{
    record Sample
    {
        public string Name { get; init; }
    }
    
    public static void Main()
    {
        var program = new Program();
            
        program.Run();
    }
    
    public void Run()
    {
        var sample1 = new Sample
        {
            Name = "ハルウララ"
        };
        
        var sample2 = sample1 with { Name = "キングヘイロー" };
        
        Console.WriteLine($"Data value = {sample1.Name}.");
        Console.WriteLine($"Data value = {sample2.Name}.");
    }
}
Data value = ハルウララ.
Data value = キングヘイロー.

その他に readonly フィールドの書き換えが init アクセサー内で可能です。ただ、ケースとして珍しいと思うので記述していません。

++C++; - プロパティ」の解説では、以下のような意見があります。

歴史的経緯で init という新キーワードが使われていますが、もし C#フルスクラッチで作り直せるなら readonly が最初から init 相当の仕様になっていたと思います。

そんなわけで、init を使ってみるのはそんなに怖いものではないです。

参考

C# System.Text.Json コンバーターを利用する方法

C# .NetJson 形式で書かれたファイルを扱う際は、2022 年現在だと System.Text.Json を利用するのがもっとも手軽だし、一般的だと思います。

このあたりと基本的な使い方については、以下の記事で記述しています。

今回は System.Text.Json でインターフェースを利用したプロパティをデシリアライズするためにコンバーターを利用する方法をメモしました。

インターフェースをデシリアライズする

public void Sample1()
{
    ISample iSample = new Sample(1, 2, "test");

    var options = new JsonSerializerOptions
    {
        WriteIndented = true,
        Converters =
        {
            // まだ未設定
        }
    };

    // シリアライズは型でエラーにならない
    var jsonText = JsonSerializer.Serialize(iSample, options);

    // デシリアライズはインターフェースを解決できないため、エラーになる
    var dSample = JsonSerializer.Deserialize<ISample>(jsonText, options);
}

System.NotSupportedException: 'Deserialization of interface types is not supported.

インターフェース型はサポートしていないという例外が出力されました。しかし、ここで変換をサポートするコンバーターを追加してやれば、変換できるようになります。

using System.Text.Json;
using System.Text.Json.Serialization;

public class InterfaceConverter<TInterface, TImplement> : JsonConverter<TInterface> where TImplement : TInterface
{
    public override TInterface? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
    {
        return (TInterface?)JsonSerializer.Deserialize(ref reader, typeof(TImplement), options);
    }

    public override void Write(Utf8JsonWriter writer, TInterface value, JsonSerializerOptions options)
    {
        JsonSerializer.Serialize(writer, value, typeof(TImplement), options);
    }
}

TInterfaceTImplement 型のクラスに変換するコンバーターです。利用したコードは以下のとおり:

public void Sample2()
{
    ISample iSample = new Sample(3, 4, "test2");

    var options = new JsonSerializerOptions
    {
        WriteIndented = true,
        Converters =
        {
            new InterfaceConverter<ISample, Sample>()
        }
    };

    var jsonText = JsonSerializer.Serialize(iSample, options);
    var dSample = JsonSerializer.Deserialize<ISample>(jsonText, options);
}

シリアライズの型を明示しなくても、デシリアライズできるようになりました。

クラスのパラメーターにジェネリック型を含む

以下のクラスの場合はどうでしょうか。

internal class SampleGenerics<T> where T : struct
{
    public T Value1 { get; set; }
    public T Value2 { get; set; }

    public SampleGenerics(T value1, T value2)
    {
        Value1 = value1;
        Value2 = value2;
    }
}

この場合は特に、コンバーターは必要ありません。デシリアライズをするときに型を指定しているためですね。(例のように intdouble に変換することもできます)

public void Sample3()
{
    var sample = new SampleGenerics<int>(5, 6);

    var options = new JsonSerializerOptions
    {
        WriteIndented = true,
        Converters =
        {
        }
    };

    var jsonText = JsonSerializer.Serialize(sample, options);
    var dSample = JsonSerializer.Deserialize<SampleGenerics<double>>(jsonText, options);
}

なので、コンバーターでインターフェースをデシリアライズするケースは、そんなに多くないと思います。むしろ、ちょっと特殊なケースが殆どだと思います。

シリアライズで指定する型だけでは処理しきれなくなったとき、コンバーターの利用すれば丁度よいときがあるので検討しよう。

サンプル

参考

C# DI コンテナと CompositeDisposable の組み合わせ

DI コンテナを利用しているプロジェクトで、リソースを破棄する処理をしようと思ったとき、CompositeDisposable を利用するという選択肢があります。

  • Window を閉じて終了するとき、Window の情報の〇〇を保存する
  • アプリを終了するとき、ゲームの情報の△△を保存する

こういう感じの終了処理はよくあるんですが、どこにコードを書いておくのかで悩むところ。コードの場所が散らばって、タイミングがズレてしまったりしても好ましくない。

CompositeDisposable を利用すると、あらかじめ登録しておいた Dispose メソッドの処理をまとめて実行することができます。なので、終了処理の起点をまとめてしまうことができるので便利なコーディングテクニックです。

ちなみに、CompositeDisposable の実装例はいくつかありますが、基本的な目的は同じです。

CompositeDisposable だけでも便利なのですが、DI パターンを利用するとコンテナ経由で VM に CompositeDisposable のインスタンス簡単に共有することができるので、とても便利だなと思いました。

今回の例では prism の Unity コンテナを利用しました。

使い方の例

  • Test1ViewModel
  • Test2ViewModel
  • MainWindowViewModel

以上の3つの VM で Dispose(終了時の処理)を実行したいとします。まず、コンテナに CompositeDisposable を登録します。App に記述して、登録と破棄 (dispose) を記述することができます。

public partial class App
{
    private CompositeDisposable Disposables = new CompositeDisposable();

    protected override Window CreateShell() => Container.Resolve<Views.MainWindow>();

    protected override void RegisterTypes(IContainerRegistry containerRegistry)
    {
        // Libraries
        containerRegistry.RegisterInstance(Disposables, "CompositeDisposable");
    }

    protected override void ConfigureModuleCatalog(IModuleCatalog moduleCatalog)
    {
        moduleCatalog.AddModule<SampleModule>();
    }

    protected override void OnExit(ExitEventArgs e)
    {
        base.OnExit(e);

        Disposables.Dispose();
    }
}

これで、(Prism の場合)プログラム終了時に OnExit が実行され、Disposables に登録された廃棄メソッドをまとめて呼び出すことができます。コードを実行する箇所もわかりやすいし、役割も明確。とても簡単ですね。(混同しちゃダメなのは、CompositeDisposable は Prism の機能ではないので自由に利用できます)

次に VM に Dispose で実行する内容をどのようにコーディングするかというと、こんな感じです。

public class MainWindowViewModel : BindableBase
{
    public MainWindowViewModel(IUnityContainer container)
    {
        var disposable = container.Resolve<CompositeDisposable>("CompositeDisposable");

        disposable.Add(() =>
        {
            Debug.WriteLine($"Called from {nameof(MainWindowViewModel)}.");
        });
    }
}

コンテナから目当ての CompositeDisposable を取り出して、リソースを解放する匿名メソッドを Add します。

CompositeDisposable を DI コンテナ経由で各 VM に伝搬するのは、便利だというのは見ての通りですね。しかし、singleton パターンでも同様のことができるのではないか、と考えるかもしれないです。

実際、singleton パターンでもやれないことはないと思います。しかし、例のように複数の複数のプロジェクトに VM を持つ場合は、singleton パターンをどのプロジェクトに作るのか困ると思います。(拡張性としても困ると思います)

また、xUnit などのテストプロジェクトを作ったとき、VM のモックをテストしづらい状況に陥いる恐れがあります。(コンストラクタの中で singleton のインスタンスから CompositeDisposable を取得することになる可能性が高い)

こんな感じで CompositeDisposable をどのようにして活用すると、より便利に扱うことができるのか(CompositeDisposable のインスタンスをどのように欲しいコードの箇所に渡すのか)もポイントだと思います。

この例だとアプリ全体の終了時にのみ CompositeDisposable を利用していますが、Unity だとシーンの切り替わりのように、リソースを解放するタイミングは色々あると思います。

サンプル

GitHub にサンプルのコードを公開しています。

アプリ終了のタイミングで、サンプルの出力を出します。

参考

Wi-Fi について基本的な整理

amazon prime day 2022 にあわせて wifi 環境を更新した。更新のため、最近の通信規格を整理したのでメモ。

まず WIFI の通信規格は「IEEE802.11」です。

無線 LAN は、もうひとつ大きな括りで、無線 LAN の中に通信方式「IEEE802.11」が含まれていて、その商標登録名称が Wi-Fi になるみたいです。(かつて IrDA 規格の赤外線通信があったし、「IEEE802.15」に bluetooth がある)

通信規格の一覧

IEEE802.11a のように規格名の後ろに「a」のような文字が付けられる。これで具体的な通信規格になる。

規格 周波数 最大通信速度 策定時期
IEEE802.11b 2.4GHz 11Mbps 1999年10月
IEEE802.11a 5.0GHz 54Mbps 1999年10月
IEEE802.11g 2.4GHz 54Mbps 2003年06月
IEEE802.11n 2.4GHz or 5.0GHz 600Mbps 2009年09月
IEEE802.11ac 5.0GHz 6.9Gbps 2014年01月
IEEE802.11ad(WiGig) 60.0GHz 6.8Gbps 2013年1月
IEEE802.11ax 2.4GHz or 5.0GHz 9.6Gbps 2021年2月9日

周波数は、ざっくりと 2.4GHz のほうが障害物に強くて遠くまで電波を届けることができます。しかし、wifi だけではなく bluetooth や家電などでも利用がある周波数なので干渉を受けやすいです。対して 5.0GHz は、wifi 専用の周波数です。他家電の干渉しづらく安定した通信になりやすいですが、障害物によって弱まりやすい弱点があります。

そこで、周波数は、デュアルバンド/トライバンドという考え方ができており、デュアルバンドだと 2.4 GHz / 5 GHz を同時にサポートし、接続機器を分散させることで、より途切れづらい環境を用意することが可能です。

通信速度の一覧

最大通信速度は、byte で考えたほうが感覚的にわかりやすいので、8で割り算して、以下のように変換します。

規格 周波数 最大通信速度(byte) 策定時期 一般呼称
IEEE802.11b 2.4GHz 1.375MB/s 1999年10月 -
IEEE802.11a 5.0GHz 6.75MB/s 1999年10月 -
IEEE802.11g 2.4GHz 6.75MB/s 2003年06月 -
IEEE802.11n 2.4GHz and 5.0GHz 75MB/s 2009年09月 Wi-Fi 4
IEEE802.11ac 5.0GHz 862.5MB/s 2014年01月 Wi-Fi 5
IEEE802.11ad(WiGig) 60.0GHz 850MB/s 2013年1月 -
IEEE802.11ax 2.4GHz and 5.0GHz 1.2GB/s 2021年2月9日 Wi-Fi 6

近く Wi-Fi 6E に対応したルーターが現れるはず。利用帯域が増える。

光回線に対応している場合は、「ac or ad or ax」に対応させると快適になるだろうことがわかります。

たとえば、amazon prime video のストリーミング再生は、標準画質で 1MB/s 以上、高画質は 5MB/s 以上の速度を推奨していますので、従来の「a or g」だとギリギリの速度になるみたいです。

今年の 6 月に私の更新をしたスマートフォンzenfone8」は以下の通信機能を有します。

  • IEEE802.11a/b/g/n/ac/ax (周波数帯域:2.4GHz/5GHz)

なので、zenfone8 を利用する都合から「ac or ax」に対応したものを選択すると、通信品質はよさそうです(周波数が異なる ad は候補になりませんね)が、Wi-Fi 5 の「ac」規格は 5.0GHz のみでした。この場合は、メッシュ Wi-Fi を構築すると安定しそうな気がします。

ストリーム数

通信速度に関連する用語として「ストリーム数」というパラメーターがあります。わかりやすくするとアンテナ数です。

親機側と受信する子機側が「MIMO (Multiple-Input and Multiple-Output) :マイモ」に対応しているとより、データ通信と品質が安定します。「ac」からは、MU-MIMO になっています。

ストリーム数が増えると高速になりけど機器価格も上がる。ac の1ストリームだと 433Mbps。MIMO の 2×2(2ストリーム)で倍の 866Mbps、3×3(3ストリーム)で 1300Mbps、4×4(4ストリーム)で 1733 Mbps と速くなっていく。(参考)

このあたりの細かい仕様は、日本語の製品仕様だと、書いてないこともあります。スマートフォンだと国別に仕様が異なることもあるので、参照しづらいですが zenfone8 だと EN には書いてありました。

  • Wireless Technology
    • Integrated WiFi 6/6E* (802.11 a/b/g/n/ac/ax, 2x2 MIMO)(URL)
    • Supports tri-band 2.4 GHz / 5 GHz/ 6 GHz WiFi

Mesh network

このほかにも、mesh network というものがあり、これは通信ネットワークの構成要素のひとつ。複数の中継機器が情報の伝送経路をお互いに形成し合います。

なので、mesh network は通信速度を上げる目的の技術ではありません。通信したい領域(範囲)に対して、領域内の電波を安定させる目的の技術です。また、mesh network は各社独自の技術で達成しているもの(たとえば、baffalo の air station connect)もありますが、Wi-Fi Alliance が提唱する「Wi-Fi EasyMesh」が技術的な標準です。これに対応するものであれば、異なるメーカーの Wi-Fi 機器であっても mesh network をサポートし合うことが可能です。

同様の通信を安定させる技術に「ビームフォーミング」というものもあります。特定の方角/方向と電波を送受信する技術です。通常の Wi-Fi の電波は指向性がないので「ビームフォーミング」は、指向性のある形状みたいなことかと思います。

参考

C# 文字列の類似度を計算する N-gram

  1. あいうえお
  2. あいうえの

上記のような文字列の類似度を計算するやり方には「n-gram」や「Jaro–Winkler distance」など色々なやり方があるみたいです。一番簡単と思ったやり方は「n-gram」でした。少し古いですが livedoor の技術ブログに紹介記事があります。

実装が簡単なのもあって ruby のコードで github公開されているリポジトリーがありました。

とりあえず、Usage に掲載されている以下例は、2つテキストの一致度 0.266... になるみたいです。githubruby コードを動かしてみます。(3-gram の例になります)

  • he is genius
  • she is cute

0が完全不一致、1が完全一致です。類似度は0~1の間に正規化された数値として表現されるみたいです。

class Test
    def trigramify(text)
        trigs = []
        text.chars.each_cons(3) { |v| trigs << v.join }
        trigs
    end
end

  test = Test.new
  data1 = test.trigramify('he is genius')
  print(data1)
  print("\n")
  data2 = test.trigramify('she is cute')
  print(data2)
  print("\n\n")

  print((data1 | data2))
  print(", count= #{(data1 | data2).size}") # 重複データを除いた合計数
  print("\n")
  print((data1 & data2))
  print(", count= #{(data1 & data2).size}") # 一致件数
["he ", "e i", " is", "is ", "s g", " ge", "gen", "eni", "niu", "ius"]
["she", "he ", "e i", " is", "is ", "s c", " cu", "cut", "ute"]

["he ", "e i", " is", "is ", "s g", " ge", "gen", "eni", "niu", "ius", "she", "s c", " cu", "cut", "ute"], count= 15
["he ", "e i", " is", "is "], count= 4

単純にデータ件数を出すと、data1 = 10 個、data2 = 9 個、一致データ数が 4 件です。なんで、計算式は  \dfrac{4}{(10+9-4)} で求めているみたいです。

ポイントは、全データの合計数を出すときに、一致テキストを data1 と data2 の2個分にしないことですね。なんで、分母から一致データ数を引いてやります。

間違いというわけではないのですが、曖昧検索 (Fuzzy-matching) を N-gram 方式で行う の記事は、最終の計算結果が 0.6666... になっていますが、これは分母の数は data1 の総数(重複なし)になっていると思います。

C# で実装する

GitHub に公開しました。

参考にしたリポジトリーは 3-gram 固定になっていたので、1 ~ 5 に対応させてみました。

一般的には 1, 2, 3 までが基本となっているみたいですが、それ以上の例もあるみたいです。wiki を参照してみてください。

参考

C# .NET で試してみる CI/CD の CI (GitHub Actions)

プログラムを書く際や書いた後、CI/CD を利用/設定することは重要です。CI とは continuous integration(継続的インテグレーション)のこと、CD とは continuous delivery(継続的デリバリー)または continuous deployment(継続的デプロイメント)のことを表します。

個人的に特に重要だと思うのは、CI の要素です。(というより CD の前提条件だし)

頑張ってテストコードを書いても、だんだんとテストを実行しないようになってしまうと、問題があります。そこで、自動的にテストを実施してくれる CI を利用しておくと、ソフトウェアの品質を守りやすくなります。

CI のやり方と基本をメモ。今回は GitHub Actions の workflows を利用しました。

GitHub Actions - workflows

CI を実装するために利用する GitHub Actions は、設定ファイル (.yml) に記述した単純なワークフローを自動的に実行してくれるサービスみたいなものです。個人利用で public リポジトリーは無料で利用することができます。private リポジトリーの場合は毎月決まった時間と容量だけ無料で利用することができます。使い切った場合は、workflow が(当月については)実行されないみたいです。

ちなみに、ワークフローを実行する仮想サーバーのことをランナーと呼びます。

A runner is a server that runs your workflows when they're triggered.

ランナーが実行するワークフローの一種で、「テストの実施」を表現できます。そこで、

  • GitHub の master に push があったら(+pull request があったら)
  • 自動的にテストを実行する(NUnit テストプロジェクトを実行する)

という .yml 設定ファイルを作成してみます。

補足すると、すこし前の頃だと、CI/CD というと appveyor を使うことが多かったと思います。現在はほとんどのケースで GitHub 標準の Github Actions で(間に合うため)利用することが多いと思います。

リポジトリーに Actions (workflow) を追加する

ブラウザで GitHubリポジトリーを開いて Actions の項目を選択します。C# のプロジェクトの場合は、「.NET」or「.NET Desktop」を選択します。この時点で設定ファイル (.yml) のテンプレートを用意してくれています。

はじめて使うときは、焦らずに設定ファイルをどのように読めばいいのか整理しながら進めましょう。とても重要です。「.NET Desktop」のファイルを読み解いてみると:

  • (env でグローバルな環境変数の作成)
  • テストの実行
  • windows application packaging の実行(+アップロード)

ざっくりと、こんな感じの job が登録されているのではないかと思います。とりあえずの CI だけなら、以下の内容でよいと思います:

  • (env でグローバルな環境変数の作成)
  • windows application packaging 用の認証取得
  • テストの実行
  • windows application packaging の実行(+アップロード)

どちらにしても、修正が必要な設定ファイル(のテンプレート)ですが、テストだけ実行する内容に修正してみます。

今回はテストプロジェクトとして個人利用しているライブラリー「HeritageLibrary」を利用しました。

name: .NET Core Desktop

on:
  push:
    branches: [ master ]
  pull_request:
    branches: [ master ]

jobs:

  build:

    strategy:
      matrix:
        configuration: [Debug, Release]

    runs-on: windows-latest  # For a list of available runner types, refer to
                             # https://help.github.com/en/actions/reference/workflow-syntax-for-github-actions#jobsjob_idruns-on

    env:
      Solution_Name: HeritageLibrary.sln
      Test_Project_Path: HeritageLibrary.Test\HeritageLibrary.Test.csproj
      
    steps:
    - name: Checkout
      uses: actions/checkout@v3
      with:
        fetch-depth: 0

    # Install the .NET Core workload
    - name: Install .NET Core
      uses: actions/setup-dotnet@v2
      with:
        dotnet-version: 6.0.x

    # Add  MSBuild to the PATH: https://github.com/microsoft/setup-msbuild
    - name: Setup MSBuild.exe
      uses: microsoft/setup-msbuild@v1.1

    # Execute all unit tests in the solution
    - name: Execute unit tests
      run: dotnet test $env:Test_Project_Path

    # Restore the application to populate the obj folder with RuntimeIdentifiers
    - name: Restore the application
      run: msbuild $env:Solution_Name /t:Restore /p:Configuration=$env:Configuration
      env:
        Configuration: ${{ matrix.configuration }}

短い内容になりました。画像で(ファイルを)補足すると、こんな感じのプロジェクトです。

うまく動かなくて、GitHub Actions をとりあえず正しく動かしたいなら、こんな設定ファイルでどうか。

name: Learn github actions
on: [push]
jobs:
  check-bats-version:
    runs-on: ubuntu-latest
    steps:
      - run: echo "hello github actions"

とにかく Actions がわからないなら、動かそう。一度動いて、パラメーターを読み解くことが大切だと思う。

シフト JIS で記述されたファイルはバグる

テストを実行したときにありがちなのは、テストプロジェクトの .cs ファイルで使用されている文字コードが「シフト JIS」であることがあります。

これをすると、見事にバグります。

.cs ファイルを開いてテキストエンコーディングを「UTF-8」変更します。最近はメモ帳でも文字コードを変更できると思います。うまく修正できました。

バッジの追加

ちゃんと CI をしているリポジトリーはエラいのです。褒め称えられるべきなのです。みんなにエラいの分かってもらえるように、README にバッジを設定するテキストを追加しましょう。

これで、コード品質を保つ努力をしていると示すことができました。

参考

GitHub markdown 書式の+α

GitHub にもドキュメントがあり、README.md のように記述しないといけない、したほうがよいものがあります。

ドキュメントですが、基本となる markdown の機能に加えて+α の便利な拡張機能があります。忘れがちですが押さえておくと、すこしリッチなドキュメントを作成することが可能です。

引用文の特殊な機能 (Warning, Note)

> **Note**  
> ブログで公開したサンプルなどをまとめています。自由に再利用可能です。

> **Warning**  
> 利用により発生した損害全てに対し、いかなる責任をも負えません。詳細はライセンスをご確認ください。

まだ beta 機能かもしれないので、見え方が変わったり変更があるかもしれないです。

リストの特殊な機能(チェックリスト)

- [x] テキスト1
- [ ] テキスト2

絵文字

:+1: のグッドマーク👍 :-1: のバッドマーク👎は、よく使われるので押さえておくと便利です。

よく使う絵文字は、短い代替テキストが用意されていたりします。

参考