0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

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

Last updated at Posted at 2025-01-12

まえがき

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

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

コードスニペット

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

public int MyProperty { get; set; }

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

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. 書式指定は文字列補完で使用可能。

var now = DateTime.Now;
Console.WriteLine($"{now:yyyyMMdd}"); // 例: 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. 引数にメソッドを渡す

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の初期化

冗長

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

分かりづらいが、よく見る気がする。
他の人と統一するためにこの書き方しか使わない。

Dictionary<string, int> ages = new Dictionary<string, int>()
{
    { "Charlie", 35 },
    { "David", 28 }
};

冗長

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

見た目が分かりやすい

Dictionary<int, string> students = new Dictionary<int, string>()
{
    [111] = "Sachin Karnik",
    [112] = "Dina Salimzianova",
    [113] = "Andy Ruth"
};

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

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

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

if (MinValue <= num && num <= MaxValue)

3. 例外の再スロー

下記のように記述できる


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

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


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

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

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

1. record

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

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.プライマリコンストラクター

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

クラスでの使用

class Product(string Name, decimal Price)
{
    public string Description => $"{Name}: {Price:C}";
}

レコード型での使用

public record Person(string FirstName, string LastName, int Age);

3.requiredプロパティ

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

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

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が未設定

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

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.式形式のメソッド

通常のメソッド定義

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

式形式

public int Add(int x, int y) => x + y;

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

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

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

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後の値が格納される。

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

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

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

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

Chapter7 コレクションの操作

Concat

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

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

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

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型の変数を持つ必要がある。

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

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メソッドの違い

abstract class GreetingBase {
    // overrideしなくてもよい
    public virtual string GetMessage() => "";
}
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にする。

class Person
{
    private string name;
}

プロパティ

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

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

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

自動実装プロパティ(Auto-Implemented Property)

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

下記と同義

class Person
{
    private string name;
    
    public string Name
    {
        get { return name; }
        set { name = value; }
    }
}

コードメトリックス

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

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

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

式形式

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?