【C#】初めてのC#コンソールアプリ作成

# 概要
VisualStudio2019を使って初めてC#のCUIアプリを作ったときのメモ
**対象読者**: 他言語の経験はあるけどC#触ったことない人

この記事で学べること↓

- クラスや非同期処理その他の文法
- コマンドライン引数の解析
- CSVファイルの読み込み
- MySQLとの接続&SQL実行
  - ※ORMではないです

# 準備
### インストール
- [VisualStudio2019](https://visualstudio.microsoft.com/ja/)をインストール
- [MySQL](https://www.mysql.com/jp/downloads/)をインストール

### プロジェクト初期化
VisualStudioを開いて`新しいプロジェクトの作成` > `コンソールアプリ(.NET Core)`
![スクリーンショット 2021-09-05 224903.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1981935/03375bb1-4a7e-7962-d430-7b5dd893f125.jpeg)
こんな感じのプロジェクトができる

# 設計
ざっくりと↓のようなアプリを作ってみたい

- CUIで動く
- CSVファイルからユーザーデータを読み込める
- MySQLにユーザーデータの作成と削除ができる

# 実装
## コマンドライン引数解析機能

### ライブラリをインストール

`ツール` > `NuGetパッケージマネージャー` > `パッケージマネージャーコンソール`をクリック
コンソールが開かれるので↓のコマンドを実行

```powershell
PM> Install-Package System.CommandLine -Version 2.0.0-beta1.21308.1
```

### コマンドライン引数解析クラス作成

`/Services/Command.cs`クラスを作成して編集

```csharp
using System;
using System.Threading.Tasks;
using System.CommandLine;
using System.CommandLine.Invocation;

namespace ConsoleApp1.Services {
    public class Command {
        private RootCommand _command;
        public Command(Func<string, string, Task<int>> handler) {
            this._command = this.InitCommand(handler);
        }

        // 非同期処理
        public async Task<int> Execute(string[] args) {
            // InvokeAsync()の処理が完了するまで待機
            return await this._command.InvokeAsync(args);
        }

        private RootCommand InitCommand(Func<string, string, Task<int>> handler) {
            var rootCommand = new RootCommand();
            rootCommand.Description = "C# Console App.";
            // コマンドライン引数の定義
            rootCommand.AddArgument(new Argument<string>("mode", "App mode e.g. create"));
            rootCommand.AddArgument(new Argument<string>("filePath", "CSV file path e.g. C:\\tmp\\test.csv"));
            // コマンドライン引数をもとに実行する処理の定義
            rootCommand.Handler = CommandHandler.Create<int, string, string>(async (int _, string mode, string filePath) => {
                await handler(mode, filePath);
            });

            return rootCommand;
        }
    }
}
```

#### 補足

- **クラスのコンストラクタ**
  - クラス名のメソッド`例) public Command() { }`がコンストラクタになる
- **非同期処理**
  - 非同期処理の戻り値は(例えばstringの場合)`Task<string>`という型にする
  - 非同期処理のメソッドには`async`修飾子をつける
  - 非同期処理が完了するまで待機させたい箇所では`await`をつける

## CSVファイル読み込み機能

### ライブラリをインストール

```powershell
PM> Install-Package CsvHelper
```

### ユーザーデータクラス作成

`/Models/UserCSV.cs`を作成して編集

```C#
using CsvHelper.Configuration.Attributes;

namespace ConsoleApp1.Models {
    public class UserCSV {
        [Name("名前")]
        public string Name { get; set; }

        [Name("年齢")]
        public int Age { get; set; }
    }
}
```

### CSV読み込みクラス作成


`/Services/CSV.cs`を作成して編集

```C#
using System.Collections.ObjectModel;
using System.Threading.Tasks;
using CsvHelper;
using ConsoleApp1.Models;

namespace ConsoleApp1.Services {
    public class CSV {
        private CsvReader _reader;

        public CSV(CsvReader reader) {
            this._reader = reader;
        }

        public async Task<ObservableCollection<UserCSV>> ReadUserCSV() {
            // UserCSVデータを入れるコレクションを生成
            var userCsvs = new ObservableCollection<UserCSV>();

            await Task.Run(() => {
                // usingステートメント
                using (this._reader) {
                    this._reader.Read();
                    this._reader.ReadHeader();
                    var records = this._reader.GetRecords<UserCSV>();

                    foreach (var record in records) {
                        userCsvs.Add(new UserCSV() {
                            Name = record.Name,
                            Age = record.Age
                        });
                    }
                };
            });

            return userCsvs;
        }
    }
}
```

#### 補足
- **usingステートメント**
  - アンマネージドリソース(ファイルなど)の使用権取得→破棄の処理を自動でやってくれる
  - 今回の場合CSVファイルの使用権取得から破棄までをusingステートメントで制御させている

## MySQL操作機能
本当はORM使いたかったけどどれがいいのかよくわからなかったので愚直にSQL文叩いていく
(そのうちC#の良さげなORM調査してみる)
### ライブラリをインストール

```C#
PM> Install-Package MySql.Data
```

### MySQL操作クラス

`/Services/MySQL.cs`を作成して編集

```csharp
using MySql.Data.MySqlClient;
using ConsoleApp1.Models;

namespace ConsoleApp1.Services {
    // struct
    public struct MySQLConfigs {
        public string server;
        public string database;
        public string user;
        public string password;
        public string characterCode;
    }

    public class MySQL {
        private MySQLConfigs _configs;
        private MySqlConnection _connection;

        public MySQL(MySQLConfigs configs) {
            this._configs = configs;
            this._connection = new MySqlConnection(this.BulildConnectionString());
        }

        public void CreateTable(string tableName) {
            this._connection.Open();
            var command = new MySqlCommand();
            command.Connection = this._connection;
            command.CommandText = $"CREATE TABLE IF NOT EXISTS {this._configs.database}.{tableName} (id INT NOT NULL PRIMARY KEY AUTO_INCREMENT, name VARCHAR(32), age INT)";
            command.ExecuteNonQuery();
            this._connection.Close();
        }

        public void CreateUser(string tableName, UserCSV user) {
            this._connection.Open();
            var command = new MySqlCommand();
            command.Connection = this._connection;
            command.CommandText = $"INSERT INTO {tableName} (name, age) VALUES (\"{user.Name}\", {user.Age})";
            command.ExecuteNonQuery();
            this._connection.Close();
        }

        public void DeleteUser(string tableName, UserCSV user) {
            this._connection.Open();
            var command = new MySqlCommand();
            command.Connection = this._connection;
            command.CommandText = $"DELETE FROM {tableName} WHERE name = \"{user.Name}\" AND age = {user.Age}";
            command.ExecuteNonQuery();
            this._connection.Close();
        }

        private string BulildConnectionString() {
            string connectionString = $"Server={this._configs.server};Database={this._configs.database};Uid={this._configs.user};Pwd={this._configs.password};Charset={this._configs.characterCode}";
            return connectionString;
        }
    }
}
```

やっていることは大体↓みたいな感じ

1. MySQLのコンフィグ作成
2. DBとコネクション貼る
3. コネクションをOpenにする
4. SQL実行する
5. コネクションをCloseする

#### 補足

- **struct(構造体)**
  - いくつかの変数をひとまとめにして扱うためのもの
  - 今回はMySQLのコンフィグをstructで定義している

## Main関数

`Program.cs`を編集

```csharp
using System;
using System.IO;
using System.Text;
using System.Globalization;
using System.Threading.Tasks;
using CsvHelper;
using ConsoleApp1.Services;

namespace ConsoleApp1 {
    class Program {
        static async Task Main(string[] args) {
            var command = new Command(Handler);
            await command.Execute(args);
        }

        private static async Task<int> Handler(string mode, string filePath) {
            if (mode != "create" && mode != "delete") {
                Console.WriteLine($"Appliction mode \"{mode}\" is invalid.");
                return 1;
            }

            var cultureInfo = new CultureInfo("ja-JP");
            var streamReader = new StreamReader(filePath, Encoding.GetEncoding("utf-8"));
            var csvReader = new CsvReader(streamReader, cultureInfo);
            var csv = new CSV(csvReader);
            var userRecords = await csv.ReadUserCSV();
            Console.WriteLine($"{userRecords.Count} users found");

            var configs = new MySQLConfigs() {
                database = "csharp_test",
                server = "localhost",
                user = "root",
                password = "password",
                characterCode = "utf8"
            };
            var mysql = new MySQL(configs);
            mysql.CreateTable("users");

            foreach (var user in userRecords) {
                if (mode == "create") {
                    Console.WriteLine($"Create user \"{user.Name}\"");
                    mysql.CreateUser("users", user);
                } else if (mode == "delete") {
                    Console.WriteLine($"Delete user \"{user.Name}\"");
                    mysql.DeleteUser("users", user);
                }
            }

            return 0;
        }
    }
}
```

# 動作確認
### コマンドライン引数の設定
アプリ実行時の引数をVisualStudio上で設定できる

`ソリューションエクスプローラー` > `ConsoleApp1`右クリック > `プロパティ` > `デバッグ`クリック
`アプリケーション引数`に引数を入力(複数入力する場合は**半角スペース**を挿入)

今回は↓を入力

```
create ${CSVファイルのパス}
```

### ビルド

`ビルド` > `ソリューションのビルド`をクリックして実行

### 実行前の準備

- MySQLで`csharp_test`という名前のデータベースを作る
- ↓のようなCSVファイルを作る

```csvs
"名前","年齢"
"太郎",20
"次郎",19
```

### 実行

![スクリーンショット 2021-09-09 161554.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1981935/4ba9bfbb-1ff8-0431-611a-8373b8e3c8e7.jpeg)

テーブルにユーザーが追加されていればOK

# 参考記事
大変参考になりました！👍
https://qiita.com/Shiho_anyplus/items/0f5bfbf73d9512802d0a
https://qiita.com/TsuyoshiUshio@github/items/02902f4f46f0aa37e4b1
https://qiita.com/4_mio_11/items/145c658078a7fe5f36a7
https://qiita.com/tnishiki/items/bfe0978592e023099588
https://qiita.com/hdk-t/items/403b3c479eb4197a92b1