今年業務で使ったc#のスキルまとめ

C# Advent Calendar 2020の17日目の記事です。

## はじめに
今年は業務でwindowsアプリを開発したので、今回は備忘録的に今年使ったスキルを書きます。
多分基礎的なことばっかりです。

- 開発環境
    - VisualStudio 2019

## レジストリ操作
インストーラーを使って開発したwindowsアプリをインストールしてもらう想定だったため、インストール時にインストーラーがレジストリ登録したバージョン情報などをアプリで表示する必要などがありました。

[インストーラーとは？](https://kotobank.jp/word/%E3%82%A4%E3%83%B3%E3%82%B9%E3%83%88%E3%83%BC%E3%83%A9%E3%83%BC-831#:~:text=%E3%82%B3%E3%83%B3%E3%83%94%E3%83%A5%E3%83%BC%E3%82%BF%E3%83%BC%E3%81%AB%E6%96%B0%E3%81%9F%E3%81%AB%E3%82%A2%E3%83%97%E3%83%AA%E3%82%B1%E3%83%BC%E3%82%B7%E3%83%A7%E3%83%B3,%E3%80%8C%E3%82%BB%E3%83%83%E3%83%88%E3%82%A2%E3%83%83%E3%83%97%E3%83%97%E3%83%AD%E3%82%B0%E3%83%A9%E3%83%A0%E3%80%8D%E3%81%A8%E3%82%82%E3%81%84%E3%81%86%E3%80%82)

windowsでレジストリを開く場合は `win + R キー`を押して`regedit`と入力する方法が一般的だと思います。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/438389/194b41a1-f629-305a-2a5a-01c5223fa7c7.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/438389/1c8d8a8c-0fc5-5d16-dd06-a959c5b6b694.png)

次はc#で`HKEY_USERS`のサブキーの一覧(.DEFAULT, S-1-5-XX)を取得します。
`using Microsoft.Win32`を追加することに注意です。

```cs
            // サブキーの一覧を取得
            var keys = Registry.Users.GetSubKeyNames();
            foreach (var key in keys)
            {
                Console.WriteLine($"取得したサブキー : {key}");
            }
```

Registry.Users で`HKEY_USERS`を読み込んで、`GetSubKeyNames()`でサブキーの一覧(string型の配列)を取得しています。

```
取得したサブキー : .DEFAULT
取得したサブキー : S-1-5-19
取得したサブキー : S-1-5-20
取得したサブキー : S-1-5-21-3102848314-395816088-1389192899-1001
取得したサブキー : S-1-5-21-3102848314-395816088-1389192899-1001_Classes
取得したサブキー : S-1-5-18
```

次はキーに書かれている値を取得してみましょう。
`HKEY_CURRENT_USER` のサブキー `Software\Microsoft\Edge\BLBeacon` の `version` の値をc#で読み込みます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/438389/7e2c2b7d-d3dc-7604-da62-7c6b0416aa10.png)

```cs
            using (var regkey2 = Registry.CurrentUser.OpenSubKey(@"Software\Microsoft\Edge\BLBeacon", false))
            {
                var ver = (string)regkey2.GetValue("version");
                Console.WriteLine($"BLBeacon ver : {ver}");
            }
```

先ほどと違うのは`Registry.CurrentUser`で`HKEY_CURRENT_USER`を指定して、`OpenSubKey()`で**Software\Microsoft\Edge\BLBeacon** のサブキーを開きます。

`GetValue()`の引数に取得したい名前を渡します。今回は`REG_SZ`の値が欲しいので、`string`でcastします。

```
BLBeacon ver : 87.0.664.60
```

最後にサブキーを作る例です。
32bit/64bitアプリでサブキーを作成する場所が変わったりするので注意が必要です。

```cs
            try
            {
                using(var prevKey = RegistryKey.OpenBaseKey(RegistryHive.LocalMachine, RegistryView.Registry64))
                using(var error = prevKey.OpenSubKey("Software", true))
                {
                    var _ = error?.CreateSubKey("test");
                    Console.WriteLine("レジストリに書き込み出来ました。");
                }
            }
            catch(Exception e)
            {
                Console.WriteLine(e.ToString());
            }
```

レジストリへのサブキー・値の作成(Create)には管理者権限が必要なので、上記のコードを管理者権限以外で実行するとCreateSubKeyで例外が発生することになります。

```
System.Security.SecurityException: 要求されたレジストリ アクセスは許可されていません。
   場所 System.ThrowHelper.ThrowSecurityException(ExceptionResource resource)
   場所 Microsoft.Win32.RegistryKey.OpenSubKey(String name, Boolean writable)
```

管理者権限で実行した場合 `HKEY_LOCAL_MACHINE\SOFTWARE\test`が作成されます。
レジストリへの書き込みを行うアプリを作る場合は、管理者権限が必要になることを注意しましょう。

[レジストリへの読み込み、書き込み](https://dobon.net/vb/dotnet/system/registrykey.html)

## json操作
設定画面でパラメータを反映できるアプリでは、起動するたびに初期値を表示するのではなく、前回アプリ終了時の設定をそのまま表示させたいと思います。
そのような場合に、どのように設定値を保存するかで`JSON（JavaScript Object Notation）形式`でデータを保持しておいて、アプリ終了時にファイルへ書き込みし、アプリ起動時にファイルを読み込むという手法がよくとられます。(少なくとも私の会社のアプリはjsonにデータもたせてます。)

jsonのライブラリはいくつかありますが、Nugetから簡単に使用できる`Newtonsoft.Json`で例を出します。

```cs
    [JsonObject("target")]
    public class TargetJson
    {
        [JsonProperty("happy")]
        public bool Boolean { get; set; }

        [JsonProperty("name")]
        public string Name { get; set; }

        [JsonProperty("pi")]
        public double Number { get; set; }

        [JsonProperty("list")]
        public List<int> List { get; set; }

        [JsonProperty("object")]
        public ObSample ObS { get; set; }
    }

    public class ObSample
    {
        [JsonProperty("currency")]
        public string Name { get; set; }

        [JsonProperty("value")]
        public double Value { get; set; }
    }
```

`TargetJson`クラスはbool、string、double、List\<int\>、stringとdoubleのプロパティをもつ`ObSampleクラス`の プロパティをもつクラスになります。

このようなオブジェクトをjsonに書き込むようにする処理を`シリアライズ`とよぶそうです。

```cs
            var t = new TargetJson()
            {
                Boolean = true,
                Name = "arisugawa",
                Number = 3.14,
                List = new List<int> { 3, 3, 4 },
                ObS = new ObSample()
                {
                    Name = "USB",
                    Value = 33.45,
                },
            };
            // 第二引数でインデントをつけれる
            string j = Newtonsoft.Json.JsonConvert.SerializeObject(t, Newtonsoft.Json.Formatting.Indented);
            // file書き込み
            File.WriteAllText(@"write.json", j);
```

変数`t`にTargetJsonクラスのインスタンスを代入し、`Newtonsoft.Json.JsonConvert.SerializeObject()`で変数tのオブジェクトをjsonに書き込める形式に変換(string型へ)してあげています。
最後に、`File.WriteAllText()` でwrite.json という名前のファイルに書き込んでいます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/438389/5752829a-6aa5-9157-becf-2b537abd6d1e.png)

上の画像がwrite.jsonに書き込まれている内容です。
JSONは `key : value` の組み合わせになり、 valueは各プロパティにセットした値ですが、keyの名前(例えばbool型のhappy)はどこで設定しているのでしょうか。

```cs
        [JsonProperty("happy")]
        public bool Boolean { get; set; }
```
`Boolean`プロパティに `JsonProperty`という属性がついていますね。ここで設定した値がjsonのkeyになります。

ファイルなどに書かれたjsonオブジェクトを読み込んで活用したい場合は`デシリアライズ`とよばれる動作が必要になります。

```cs
            var sr = new StreamReader("write.json");
            // ファイルの内容をすべて読み込みます。
            string j = sr.ReadToEnd();
            // string型の文字列をもとにTargetJson型のオブジェクトにデシリアライズ
            TargetJson s = Newtonsoft.Json.JsonConvert.DeserializeObject<TargetJson>(j);
            // オブジェクトのプロパティにアクセス
            Console.WriteLine(s.ObS.Name);
```


## バージョニング

個人でつくるHelloWorldやサンプルアプリならバージョンについて深く考えることはないかと思いますが、お客様提供するアプリの場合は障害発生したときの解析や問い合わせ対応のためにバージョンを付与するのは必須かと思います。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/438389/8a6ccb37-9384-0e07-81f6-9048dcc8649f.png)

> VisualsStudio2019(devenv.exe)のファイルバージョン

バージョニングのルールやどのタイミングでどの番号をいくつ上げるかなどは所属する組織で違うはずですが、
windowsアプリの場合はMicrosoftのドキュメントに倣っているところが多いのではないでしょうか。

```cs
// AssemblyInfo.csの内容
// アセンブリのバージョン情報は次の 4 つの値で構成されています:
//
//      メジャー バージョン
//      マイナー バージョン
//      ビルド番号
//      リビジョン
//
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]
```

バージョン情報の更新は、`Properties\AssemblyInfo.cs`の"1.0.0.0"の内容を直接書き換えても反映されます。が、`プロジェクト-右クリック、プロパティ-アプリケーション-アセンブリ情報`で表示されるGUI上で変更するほうが楽かと思います。ちなみにこのGUIで変更した内容はAssemblyInfo.csに反映されます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/438389/7f68a55c-5378-199d-6e30-80a1b10838bf.png)


## アプリのアイコン設定
visualstudioで作成したアプリはデフォルトのアイコンになります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/438389/74de1ddf-ab45-87b3-f0b3-4f1c18b0460b.png)

アイコンを変更する場合は
`プロジェクト-右クリック、プロパティ-アプリケーション-リソース-アイコンとマニフェスト`で *ico*ファイルを指定します。

icoファイルはjpgやpngをコンバートして作成しましょう。
[アイコンファイル（*.ico）作成](https://qiita.com/Kosen-amai/items/4700100342c76f9fda78)

icoファイルを設定後は、プロジェクトにicoファイルが追加されます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/438389/a51f2d75-a360-b2df-5b52-3d8bca7056e5.png)

ビルドしてexeを生成・実行すると指定したicoがアイコンとして表示されているはずです。


## まとめ

コーディング的なスキルというより、リリースまでの設定に近い部分が多くなりました。
windowsアプリだとUWPによるアプリ開発が主流と思いますが、今回はいわゆるレガシーアプリ開発で、開発のルールなどもUWP開発とは少し違うことになりました。(UWPだとマニフェストの設定やstoreへの登録などが必要ですね。)

正直、ドキュメントがチームに充実しているかで変わってきますね。
以上になります。

### 備考
[インストールシールド12](https://www.networld.co.jp/is/is2012_spring/overview.htm)
業務ではIS12を使ってインストーラーを開発してます。

[きまま研究所(WOW64で起動した際に64bitレジストリへアクセスする)](https://aonasuzutsuki.hatenablog.jp/entry/2015/12/08/17381)