はじめに
数年間業務で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: 検索開始位置
- public Regex(string pattern [, RegexOptions])
// 文章から郵便番号だけを抜き出す
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型の方が便利そう。