2
2

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 2024-09-04

はじめに

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

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

学びになったこと

1章 イントロダクション

  • トップレベルステートメント(C#9.0)

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

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

2章 C#の基本

  • 数値セパレータ(C#7.0)

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

var num = 12_34_56;
  • $(変数展開)と@(逐語的文字列リテラル)
// $をつけると変数を埋め込める
var name = "鈴木";
var massage = $"こんにちは、{name}さん";

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

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

3章 演算子

  • (結合する数が多い場合の)文字列結合はStringBuilder()を使うと速い。
// 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進数を用いるため、丸目誤差を防ぐ。ただし、パフォーマンスとのトレードオフなので、必要な場面で使う。

// 出力結果:0.6000000000000001
Console.WriteLine(0.2 * 3);

// 出力結果:0.6
Console.WriteLine(0.2M * 3);
  • String型は参照型だが==演算子を上書きしてEqualsメソッドを呼び出しているので、例外的に値同士の比較が可能。
var st1 = "こんにちは!";
var st2 = "こんにちは!";
Console.WriteLine(st1 == st2); // 出力結果:True

4章 制御構文

  • (オーバーヘッドが大きいため)ループ内でのオブジェクト生成は避ける。
  • break/continue文は現在のループだけを脱出/スキップする。
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: 検索開始位置
// 文章から郵便番号だけを抜き出す
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)
var flowerDict = new Dictionary<string, string>()
{
	// KeyとValueが分かれているので可読性が高い
    ["Rose"] = "バラ",
    ["Sunflower"] = "ひまわり",
    ["Morning Glory"] = "あさがお"
};

if (flowerDict.ContainsKey("Rose"))
{
    var flower = flowerDict["Rose"];
}
  • リスト(List) vs セット(Set)
    • リスト:要素の重複可。要素の挿入・削除する用途。
    • セット:要素の重複不可。要素の包含関係を判定する用途。

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

  • オーバーロード

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

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

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

public void WriteLog(int data)
{
    Console.WriteLine($"ログ出力結果:{data}");
}
  • ローカル関数(C#7.0)

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

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

    Console.WriteLine(Discount(1000, 0.2));
}
  • タプル型(C#7.0)

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

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

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

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

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

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

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

// 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クラスに対する拡張メソッド

// 静的クラスで宣言しなければならない
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句)を指定することで、例外の細かい振り分けが可能になる。

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

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

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としてデータをグループ化する。

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を使って書くことができる。

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から値を取得する

using Newtonsoft.Json;

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

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

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

2
2
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
2
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?