【読書メモ】実践で役立つC#プログラミングのイディオム／定石パターン

## まえがき

最近、仕事でC#を使っている。Javaの経験があるのでC#の勉強をしなくても特に困ることはないが、雰囲気で使うのではなく、しっかり自分のものにしたいと思いC#の本を読み始めた。仕事のパフォーマンスを上げたい、仕事の経験を自分の糧にしたいという思いがある。
この記事では、「実践で役立つC#プログラミングのイディオム／定石パターン」を読んで、参考になった個所のメモを残していく予定。

## Chapter1 オブジェクト指向プログラミングの基礎

#### コードスニペット

プロパティを定義したい場所で”prop”とタイプした後にTabキーを2回押すと、以下のように自動実装プロパティのひな型がエディタに挿入される。

```csharp
public int MyProperty { get; set; }
```

自動でintのところにカーソルが移動する。ここでstringを入力してTabキーを2回押す。
自動でMyPropertyのところにカーソルが移動する。ここでNameと入力し、Enterキーを押す。
これで、以下のプロパティが完成する。

```csharp
public string Name { get; set; }
```

foreach分は”fore”＋Tab2回。
コンストラクタは”ctor”＋Tab2回。

## Chapter2 C#でプログラムを書いてみよう

#### 1. 標準の数値書式指定文字列
標準の数値書式指定文字列は、1つのアルファベットと必要に応じて数字（精度指定子）で構成される。

"C" または "c": 通貨形式
例: 123.456.ToString("C2") → $123.46

"D" または "d": 10進数形式
例: 123.ToString("D5") → 00123

"F" または "f": 固定小数点形式
例: 123.456.ToString("F2") → 123.46

"E" または "e": 指数形式
例: 1234.567.ToString("E2") → 1.23E+003

#### 2. カスタム数値書式指定文字列

カスタム数値書式指定文字列では、より柔軟な書式設定が可能。

"#": 必要に応じて数字を表示
"0": 必ず数字を表示（ない場合は0）
",": 3桁ごとの区切り文字
".": 小数点

#### 3. 書式指定は文字列補完で使用可能。
```csharp
var now = DateTime.Now;
Console.WriteLine($"{now:yyyy年MM月dd日}"); // 例: 2025年01月13日

var num = 1234567890;
Console.WriteLine($"{num:#,0}"); // 出力: 1,234,567,890
```
#### 4. public constとstatic readonly

public constには、バージョン管理問題がある。

C#のconstのバージョン管理問題（バージョニング問題）とは、public constで定義された定数を含むライブラリを更新した際に発生する問題です。具体的には以下の特徴があります：
const定数はコンパイル時にリテラルと同じように値が展開されます。
ライブラリ側でconst定数の値を変更しても、それを参照しているアプリケーション側には自動的に反映されません。
変更を反映させるためには、ライブラリだけでなく、それを参照しているアプリケーション側も再コンパイルする必要があります

この問題を回避するために、以下の対策が推奨されます：
public constの代わりに、static readonlyを使用する。
値が絶対に変わらないもの（例：Math.PI）以外は、public constを使用しない


#### 5. internal

同じプロジェクト内からのみアクセス可能。

## Chapter3 ラムダ式とLINQの基礎

#### 1. 引数にメソッドを渡す

```csharp
static int Count(int[] numbers, Func<int, bool> judge)
{
    int count = 0;
    foreach (int number in numbers)
    {
        if (judge(number))
        {
            count++;
        }
    }
    return count;
}
var judge = (int x) => x > 5;
var numbers = new int[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
var count = Count(numbers, judge);
Console.WriteLine(count);
```
## Chapter4 基本イディオム

#### 1. Dictionaryの初期化

冗長
```csharp
Dictionary<string, int> ages = new Dictionary<string, int>();
ages["Alice"] = 25;
ages["Bob"] = 30;
```

分かりづらいが、よく見る気がする。
他の人と統一するためにこの書き方しか使わない。
```csharp
Dictionary<string, int> ages = new Dictionary<string, int>()
{
    { "Charlie", 35 },
    { "David", 28 }
};
```

冗長
```csharp
Dictionary<string, int> ages = new Dictionary<string, int>();
ages.Add("Eve", 22);
ages.Add("Frank", 40);
```

見た目が分かりやすい
```csharp
Dictionary<int, string> students = new Dictionary<int, string>()
{
    [111] = "Sachin Karnik",
    [112] = "Dina Salimzianova",
    [113] = "Andy Ruth"
};
```

LINQのToDictionaryメソッドでDictionaryを作ることもできる。
とても便利。

#### 2. 数値が範囲内にあるかチェック

比較対象の変数を常に左に書くよりも直感的に理解しやすい

```csharp
if (MinValue <= num && num <= MaxValue)
```
#### 3. 例外の再スロー

下記のように記述できる

```csharp

try {
 } catch (FileNotFoundException ex) {
     throw;
 }

```

下記のように記述すると、スタックトレース情報が消えるらしい！？

```csharp

try {
 } catch (FileNotFoundException ex) {
     throw ex;
 }

```

C#の仕様らしい
https://qiita.com/tokishirazu/items/b510ebfdb7ff089fe44c

## Chapter5 クラスに関するイディオム

#### 1. record

Equals、GetHashCode、ToStringがオーバーライドされている。
JavaのLombokに似たような機能があった。

```csharp
public record Person(string FirstName, string LastName);

class Program
{
    static void Main()
    {
        // record型のインスタンスを作成
        var person1 = new Person("John", "Doe");
        var person2 = new Person("John", "Doe");

        // 同じ値を持つ別のインスタンスを作成
        Console.WriteLine(person1 == person2); // True (値が同じだから)

        // プロパティの取得
        Console.WriteLine(person1.FirstName);  // John
        Console.WriteLine(person1.LastName);   // Doe
    }
}

```

#### 2.プライマリコンストラクター

クラスや構造体の定義時に、型名の直後にパラメーターリストを記述することで、コンストラクターを簡潔に定義する機能

クラスでの使用
```csharp
class Product(string Name, decimal Price)
{
    public string Description => $"{Name}: {Price:C}";
}
```

レコード型での使用
```csharp
public record Person(string FirstName, string LastName, int Age);
```

#### 3.requiredプロパティ

オブジェクト初期化時に特定のプロパティやフィールドを必ず初期化することを強制するための修飾子。

使い方
※コンストラクタではなく、オブジェクト初期化子で初期化する

```csharp
public class Person
{
    public required string FirstName { get; init; }
    public required string LastName { get; init; }
    public int? Age { get; init; } // requiredではない
}

// 正しい使い方
var person = new Person
{
    FirstName = "John",
    LastName = "Doe"
};

// 以下はコンパイルエラー
// var person = new Person(); // FirstNameとLastNameが未設定

```

コンストラクタと併用する場合

```csharp
public class Person
{
    public required string FirstName { get; init; }
    public required string LastName { get; init; }

    [System.Diagnostics.CodeAnalysis.SetsRequiredMembers]
    public Person(string firstName, string lastName)
    {
        FirstName = firstName;
        LastName = lastName;
    }
}

// コンストラクターで初期化
var person = new Person("John", "Doe");
```

#### 4.式形式のメソッド

通常のメソッド定義

```csharp
public int Add(int x, int y)
{
    return x + y;
}
```

式形式
```csharp
public int Add(int x, int y) => x + y;
```

単純なメソッドにだけ使うのが良いらしい
https://qiita.com/hysui/items/1628d0f4e8e9271697f3

#### 5.コンストラクタ初期化子

コンストラクタ引数の後に:thisを記述することで、別のコンストラクタを呼び出せる。
既存クラスを継承して使ったことがあった気がする。

```csharp
public class Person
{
    public string Name { get; }
    public int Age { get; }

    // 引数なしコンストラクター
    public Person() : this("Unknown", 0) // 別のコンストラクターを呼び出し
    {
        Console.WriteLine("引数なしコンストラクターが呼ばれました");
    }

    // 引数ありコンストラクター
    public Person(string name, int age)
    {
        Name = name;
        Age = age;
        Console.WriteLine($"引数ありコンストラクターが呼ばれました: Name={name}, Age={age}");
    }
}

// 使用例
var person1 = new Person();               // 引数なしコンストラクターを呼び出し
var person2 = new Person("Alice", 25);    // 引数ありコンストラクターを直接呼び出し

```

## Chapter6 文字列操作

#### TryParse

型チェックと変換を同時に行えるメソッド。
TryParseはboolを返却する。
TryParseの第2引数にoutと変数宣言を記述する。
宣言した変数にParse後の値が格納される。

```csharp
var str = "123";
if (int.TryParse(str, out int num) )
{
    Console.WriteLine(num + 10);
}
else
{
    Console.WriteLine("strは整数ではありません。");
}
```

#### 数値をカンマ区切り文字列に変換

NはNumberを意味する。
Numberを綺麗に見せるフォーマットという意味。
綺麗な数値にたまたまカンマ区切りが採用されている。
0は小数点以下を表示しないという意味。

```csharp
var num = 123456;
var str = num.ToString("N0"); //123,456
```

## Chapter7 コレクションの操作

#### Concat

2つのコレクションをつなげたコレクションを返す。
元のコレクションは変更しない。

```csharp
var list1 = new List<int> {1, 2, 3};
var list2 = new List<int> {4, 5, 6};
var combined = list1.Concat(list2).ToList(); // 新しいリストが生成される
```

AddRangeの場合は元のコレクションが変更される

```csharp
var list1 = new List<int> {1, 2, 3};
var list2 = new List<int> {4, 5, 6};
list1.AddRange(list2); // list1が変更される
```
## Chapter13 LINQ

inner join

Joinメソッドで書籍一覧（Books）とカテゴリ一覧（Categories）をカテゴリIDで結合

Joinメソッドの引数

>第1引数　　結合する2番めのシーケンス 
第2引数　　対象シーケンスの結合キー 
第3引数　　2番めのシーケンスの結合キー 
第4引数　　結合した結果として得られるオブジェクトの生成関数

※AとBをJoinするためには、AがB型の変数を持つ必要がある。

```csharp
var books = Library.Books
     .Join(Library.Categories,
         book => book.CategoryId,
         category => category.Id,
         (book, category) => new {
             book.Title,
             Category = category.Name,
             book.PublishedYear
         }
     )
     .OrderBy(b => b.PublishedYear)
     .ThenBy(b => b.Category);
 foreach (var book in books) {
     Console.WriteLine($"{book.Title}, {book.Category}, {book.PublishedYear}");
 }
```

left join

```csharp
var books = dbContext.Books
    .GroupJoin(dbContext.Categories,
        book => book.CategoryId,
        category => category.Id,
        (book, categories) => new { book, categories })
    .Select(
        bookWithCategories => new {
            bookWithCategories.book.Title,
            Categories = bookWithCategories.categories,  // categoriesをそのまま保持
            bookWithCategories.book.PublishedYear
        })
    .OrderBy(b => b.PublishedYear)
    .ThenBy(b => b.Categories.FirstOrDefault()?.Name);  // Categoriesの中で最初のカテゴリでソート

```
## Chapter15 実践的オブジェクト指向プログラミング

#### abstractクラスのvirtualメソッドとabsctractメソッドの違い

```csharp
abstract class GreetingBase {
    // overrideしなくてもよい
    public virtual string GetMessage() => "";
}
```

```csharp
abstract class GreetingBase {
    // overrideが必須
    public abstract string GetMessage();
}
```
#### ライブラリの作り方

Visual Studioで新規プロジェクトを作成する際に、［クラスライブラリ］（「クラスライブラリ」の後ろに括弧付きの表記がないもの）を選択

クラスを作成

ソリューションのビルドを行う

クラスライブラリHoge.dllができる。

作成したライブラリをプロジェクトの参照に追加する。

usingでクラスの名前空間を指定する。

## Chapter17 スタイル、ネーミング、コメント

#### Pascal形式とCamel形式

| 識別子        | 形式   | 例              |
|---------------|--------|-----------------|
| 名前空間      | Pascal | System.Text     |
| クラス        | Pascal | StringBuilder   |
| インターフェース | Pascal | IEnumerable     |
| 構造体        | Pascal | DateTime        |
| 列挙型        | Pascal | DayOfWeek       |
| 列挙値        | Pascal | Monday          |
| イベント      | Pascal | MouseHover      |
| メソッド      | Pascal | GetHashCode     |
| プロパティ    | Pascal | ViewData        |
| 定数          | Pascal | MaxValue        |
| フィールド    | Camel  | _currentPosition |
| 引数          | Camel  | options         |
| ローカル変数  | Camel  | itemName        |

#### Pascal形式とCamel形式(2文字の英単語)

| 英単語         | Pascal | Camel |
|----------------|--------|-------|
| DB (Database)  | DB     | db    |
| IO (Input/Output) | IO   | io    |
| IP (InternetProtocol) | IP | ip |
| ID (Identity)  | Id     | id    |
| OK              | Ok     | ok    |
| go              | Go     | go    |
| on              | On     | on    |

## Chapter18 良いコードを書くための指針

#### フィールド

✅フィールドはprivateにする。

```csharp
class Person
{
    private string name;
}
```

#### プロパティ

フィールドをprivateにしてプロパティでアクセスできるようにする。

```csharp
class Person
{
    private string name; // private フィールド

    public string Name  // プロパティ
    {
        get { return name; } 
        set { name = value; }
    }
}
```

#### 自動実装プロパティ（Auto-Implemented Property）

```csharp
class Person
{
    public string Name { get; set; } // 内部で private フィールドを自動生成
}
```

下記と同義

```csharp
class Person
{
    private string name;
    
    public string Name
    {
        get { return name; }
        set { name = value; }
    }
}
```
#### コードメトリックス

コードメトリックスを計算するには、Visual Studioのメニューで［分析］-［コードメトリックスを計算する］-［選択したプロジェクト用］を選択します。

#### コンパイラの警告を無視しない

本当に重要な警告を無視してしまう危険性があるため。


#### 式形式

https://qiita.com/hysui/items/1628d0f4e8e9271697f3