『独習C#』で学びになったことまとめ

# はじめに

数年間業務でC#を使っているが、体系的に勉強したことがなかった。
『独習C#』を読んで特に学びになったポイントをまとめる。

※一部、書籍の内容とは異なる点もありますのでご注意ください。

https://www.shoeisha.co.jp/book/detail/9784798175560

# 学びになったこと

### 1章 イントロダクション

- トップレベルステートメント（C#9.0）

C#がusingやクラス、Mainメソッドなどの定型句を補ってくれる機能。VS2022では標準搭載されており、Mainメソッドとの併用はできない。

```csharp
// program.csにこの1行だけ書けば動く
Console.WriteLine("Hello, World!");
```

### 2章 C#の基本

- 数値セパレータ（C#7.0）

桁数の大きな数値の**可読性**を改善するために桁区切り文字(_)で区切る機能。

```csharp
var num = 12_34_56;
```

- $(変数展開)と＠(逐語的文字列リテラル)

```csharp
// $をつけると変数を埋め込める
var name = "鈴木";
var massage = $"こんにちは、{name}さん";

// @をつけるとエスケープシーケンスを無効にできる
var filePath = @"C:\\Users\\NM\\OneDrive\\";

// 組み合わせ
var directoryName = "TEST";
var filePath2 = $@"C:\\Users\\NM\\OneDrive\\{directoryName}";
```

### 3章 演算子

- （結合する数が多い場合の）文字列結合はStringBuilder()を使うと速い。

```csharp
// BAD
// 結合の度に新しいオブジェクトが生成されるため、パフォーマンスが悪い
var message = "";
for(int i =0; i< 1000; i++)
{
    message += "さよなら";
}

// GOOD
// サイズを拡張しながら文字列を追加し、最後に文字列オブジェクトを生成するため、パフォーマンスが良い
var builder = new StringBuilder();
for(int i=0; i < 1000; i++)
{
    builder.Append("さよなら");
}
var message = builder.ToString();
```

- 浮動小数点の演算にはMをつけてdecimal型にする。

decimal型はdouble型に比べて有効桁数が多く内部的な演算にも10進数を用いるため、丸目誤差を防ぐ。ただし、パフォーマンスとのトレードオフなので、必要な場面で使う。

```csharp
// 出力結果：0.6000000000000001
Console.WriteLine(0.2 * 3);

// 出力結果：0.6
Console.WriteLine(0.2M * 3);
```

- String型は参照型だが==演算子を上書きしてEqualsメソッドを呼び出しているので、例外的に値同士の比較が可能。

```csharp
var st1 = "こんにちは！";
var st2 = "こんにちは！";
Console.WriteLine(st1 == st2); // 出力結果：True
```

### 4章 制御構文

- （オーバーヘッドが大きいため）ループ内でのオブジェクト生成は避ける。
- break/continue文は現在のループだけを脱出/スキップする。

```csharp
var list = new List<int>() { 1, 2, 3, 4, 5 };
var list2 = new List<string>() { "あ", "い", "う", "え", "お" };

foreach(var l in list)
{
    foreach(var l2 in list2)
    {
        Console.WriteLine(l2);
        if(l2 == "う")
        {
            break; // 現在のループだけを抜ける
        }
    }
}
```

### 5章 標準ライブラリ

- 正規表現(Regular Expression)の基本的な使い方
クラス
    - public Regex(string pattern [, RegexOptions])
        - pattern: 正規表現パターン
        - options: 正規表現オプション
    
    メソッド
    
    - public MatchCollection Matches(string input [,int startat])
        - input: 検索する文字列
        - startat: 検索開始位置

```csharp
// 文章から郵便番号だけを抜き出す
var str3 = "住所は〒184-0000 鎌ヶ谷市梶野町0-0-0です。\nあなたの住所は〒273-0000 大野町0-0-0ですね。";
var rgx = new Regex(@"〒(\d{3})-(\d{4})");
var match = rgx.Matches(str3);

if(match.Count > 0)
{
    foreach(Match m in match)
    {
        Console.WriteLine(m.Value);
    }
}
```

### 6章 コレクション

- Dictionaryはインデックス初期化子を使用する(C#6)

```csharp
var flowerDict = new Dictionary<string, string>()
{
	// KeyとValueが分かれているので可読性が高い
    ["Rose"] = "バラ",
    ["Sunflower"] = "ひまわり",
    ["Morning Glory"] = "あさがお"
};

if (flowerDict.ContainsKey("Rose"))
{
    var flower = flowerDict["Rose"];
}
```

- リスト(List) vs セット(Set)
    - リスト：要素の重複可。要素の挿入・削除する用途。
    - セット：要素の重複不可。要素の包含関係を判定する用途。

### 7章 オブジェクト指向構文(基本)

- オーバーロード

名前は同じで引数の型/並びだけが異なるメソッドを複数定義すること。

具体例な用途としては、同じ処理が重複して書かれるのを防ぐため、別のオーバーロードを呼び出して使われる。

```csharp
public void WriteLog(List<int> datas)
{
    foreach (var data in datas)
    {
		// 別のオーバーロードを呼び出す
        WriteLog(data);
    }
}

public void WriteLog(int data)
{
    Console.WriteLine($"ログ出力結果：{data}");
}
```

- ローカル関数(C#7.0)

メソッドの配下で宣言し、メソッド内でのみ利用できる関数。

```csharp
internal void Main()
{
    double Discount(int price, double rate)
    {
        return price * (1 - rate);
    }

    Console.WriteLine(Discount(1000, 0.2));
}
```

- タプル型(C#7.0)

複数の値を束ねるための型。メソッドから複数の値を返したいが、わざわざクラスを作るまでもないような場合に使用。

```csharp
/// <summary>
/// リストの最大値と最小値を返却するメソッド
/// </summary>
internal (int max, int min) GetMinMax(List<int> list)
{
    return (list.Max(), list.Min());
}
```

### 8章 オブジェクト指向構文(カプセル化/継承/ポリモーフィズム)

- publicなフィールド変数でなくプロパティを使うべき理由
    - 読み書き(get/set)の許可不許可を制御するため
    - 読み書きの際に検証/加工するため
&nbsp;
- 仮にシンプルなgetter/setterの場合でもプロパティを使うべき理由
    - 今後、検証/加工が必要になった場合の拡張性を担保するため
    - データバインディング、属性の付与をするため
    - インターフェースで使用するため
&nbsp;
- どのような場合に継承を利用するべきか

親クラスと子クラスに、is-aの関係が成り立つかを確認。例えば「BusinessPerson」は「Person」なので、BusinessPersonはPersonの継承関係は妥当。

- メソッドの隠蔽は使うべきでない理由
    - 基底クラスに影響せず派生クラスのみで利用できる。意図しないメソッドを上書きしてしまい、正しく動作しなくなる可能性がある。
    - ポリモーフィズム（同じ名前のメソッドで異なる挙動を実現すること）が実現できない。

例）隠蔽とオーバーライドの例

```csharp
// BAD 隠蔽
internal class Person
{
    public string Name { get; set; }
    public void Show() 
    {
        Console.WriteLine($"人間の{this.Name}です。");
    }
}

internal class BusinessPerson : Person
{
    // メソッドの隠蔽(基底クラスのメソッドを派生クラスのメソッドで隠蔽する)
    public new void Show()
    {
        Console.WriteLine($"会社員の{this.Name}です。");
    }
}

// GOOD オーバーライド
internal class Person
{
    public string Name { get; set; }
    
    // 仮想メソッドで上書きを許可することを明示する
    public virtual void Show() 
    {
        Console.WriteLine($"人間の{this.Name}です。");
    }
}

internal class BusinessPerson : Person
{
    // オーバーライド
    public override void Show()
    {
        Console.WriteLine($"会社員の{this.Name}です。");
    }
}
```

- 拡張メソッド

既存のクラスに対して、継承を使わずに、メソッドだけを追加する仕組み。

例）Stringクラスに対する拡張メソッド

```csharp
// 静的クラスで宣言しなければならない
internal static class StringExtensions
{
    // 静的メソッドの第一引数の型の前にthisをつけると拡張メソッドになる
    public static string ToTitleCase(this string str)
    {
        // 先頭を大文字、それ以外を小文字に変換
        str = str.ToLower();
        var s = str.Substring(0, 1).ToUpper();
        return str.Replace(str.Substring(0, 1), s);
    }

    public static void Main()
		{
	    var hello = "hello";
	    // メソッドのように呼び出せる
	    Console.WriteLine(hello.ToTitleCase()); // Hello
		}
}
```

### 9章 オブジェクト指向構文(名前空間/例外処理/ジェネリック)

- 例外フィルタ(C#6.0)

catchブロックに条件句(when句)を指定することで、例外の細かい振り分けが可能になる。

```csharp
try
{
    var file = new StreamReader(@"C:\test.txt");
}
catch(FileNotFoundException ex) when (ex.Message.Contains(".txt"))
{
    Console.WriteLine("存在しないテキストファイルが指定されました。");
}
catch (FileNotFoundException)
{
    Console.WriteLine("存在しないファイルが指定されました。");
}
```

### 10章 ラムダ式/LINQ

- LINQ: Distinctメソッド

取り出したデータから重複を除去する。

```csharp
var list = new List<int> { 1, 2, 3, 4, 5, 3, 1 };
var distinctList = list.Distinct().ToList(); // 出力結果：1, 2, 3, 4, 5
```

- LINQ: GroupByメソッド

特定のプロパティをKeyとしてデータをグループ化する。

```csharp
var books = new List<Book>
{
    new Book { Id = 1, Title = "吾輩は猫である", Publisher = "新潮社", PublishYear = 1905 },
    new Book { Id = 2, Title = "坊ちゃん", Publisher = "新潮社", PublishYear = 1906 },
    new Book { Id = 3, Title = "ノルウェイの森", Publisher = "講談社", PublishYear = 1987 },
    new Book { Id = 4, Title = "ONE PIECE", Publisher = "集英社", PublishYear = 1997 },
    new Book { Id = 5, Title = "坊ちゃん", Publisher = "新潮社", PublishYear = 1906 }, // 重複
};

var group = books.GroupBy(x => x.Publisher);
foreach (var g in group)
{
    Console.WriteLine($"出版社：{g.Key}のベストセラー");
    foreach (var book in g)
    {
        Console.WriteLine($"タイトル：{book.Title}");
    }
}

/* 出力結果 */
// 出版社：新潮社のベストセラー
// タイトル：吾輩は猫である
// タイトル：坊ちゃん
// タイトル：坊ちゃん
// 出版社：講談社のベストセラー
// タイトル：ノルウェイの森
// 出版社：集英社のベストセラー
// タイトル：ONE PIECE
```

オブジェクトのリストから重複を削除する場合もGroupByを使って書くことができる。

```csharp
var distinctBooks = books
    .GroupBy(b => b.Title)
    .Select(g => g.First())
    .ToList();

foreach (var book in distinctBooks)
{
    Console.WriteLine($"タイトル：{book.Title}");
}

/* 出力結果 */
// タイトル：吾輩は猫である
// タイトル：坊ちゃん
// タイトル：ノルウェイの森
// タイトル：ONE PIECE
```

### 11章 高度なプログラミング

- dynamic型

実行時まで型が決まらない動的オブジェクトを扱うための型

例）dynamic型を利用してJSONから値を取得する

```csharp
using Newtonsoft.Json;

dynamic json = JsonConvert.DeserializeObject(
    @"{""name"":""鈴木"",""age"":30}"
);

Console.WriteLine(json.name);   // 出力結果：鈴木
Console.WriteLine(json.age);    // 出力結果：30
```

System.Text.Jsonを使うとdynamicを使わずに変換後の型をがっちり決める必要があるので、一部だけを簡易的に取得するならdynamic型の方が便利そう。