本ツールで使用しているオープンソースライブラリ

本ツールのコードには 1 個のオープンソースライブラリが含まれています。

C# チートシート — クイック リファレンス

C# 12(.NET 8 併用)の構文、OOP、LINQ、そして最もよく使う標準ライブラリを網羅したクイック リファレンス。日常の約 80% のシナリオをカバーします。

C#

C# C# 12 (with .NET 8)

.NET (Core / 5+ / 8) · OOP、ジェネリック、関数型、並行 · 静的、強い型付け、名前的型付け

おすすめの学習パス

まず「Hello World とビルド環境」(dotnet new console)を動かす → 変数、型、制御フローに慣れる → コレクションと LINQ でデータを処理する → オブジェクト指向(class / インターフェース)を理解する → async / await を習得する → 最後に必要に応じてファイル、ネットワーク、正規表現、ビルド / デバッグを参照してください。FAQ セクションは後から戻って落とし穴を回避するのに役立ちます。

1.Hello World とビルド環境

.NET プログラムを一から作成・実行・構成します: トップレベル ステートメント、プロジェクト ファイル、名前空間、コマンドライン引数。

最小プログラム

C# 9+ はトップレベル ステートメントをサポートします。Program.cs に直接実行コードを書き、class や Main の定型句は不要です。Console.WriteLine で行を出力します。

1
2
3
// Program.cs
Console.WriteLine("Hello, world!");
// トップレベルステートメント(C# 9+):コンパイラが Main エントリを自動生成

作成と実行

dotnet CLI は .NET のコマンドライン エントリ ポイントです。dotnet new でプロジェクト作成、dotnet run でビルドと実行、dotnet build でビルドのみ。

1
2
3
// $ dotnet new console -n app
// $ cd app && dotnet run
// $ dotnet build // コンパイルのみ、bin/Debug を生成

コマンドライン引数

トップレベル ステートメントの args は string[] のコマンドライン引数で、args[0] からがユーザー引数です。従来の Main(string[] args) と同等です。

1
2
3
4
5
6
7
// Program.cs
if (args.Length > 0) {
Console.WriteLine($"Hello, {args[0]}!");
} else {
Console.WriteLine("No args");
}
// $ dotnet run -- Nick

終了コード

トップレベル ステートメントでは return で終了コードを指定できます。0 は成功、それ以外はエラーの種類を表します。Environment.ExitCode も読み書き可能です。

1
2
3
// Program.cs
return 0; // トップレベルステートメントで整数を直接 return
// または Environment.ExitCode = 1;

名前空間

namespace は型を整理して名前の衝突を防ぎます。ファイル スコープ名前空間(C# 10)はセミコロンで宣言し、中括弧とインデントを省きます。

1
2
3
4
5
6
namespace App.Utils; // ファイルスコープ名前空間(C# 10)
public class Helper {
public static int Twice(int x) => x * 2;
}
// アクセス:App.Utils.Helper.Twice(3)

using ディレクティブ

using ディレクティブは名前空間をインポートし、完全修飾名を不要にします。GlobalUsings.cs のグローバル using でプロジェクト全体にインポートを共有できます。

1
2
3
4
5
using System;
using System.Collections.Generic;
// GlobalUsings.cs
// global using System.Linq; // グローバル using(C# 10)
// global using static System.Console;

トップレベル ステートメントの詳細

トップレベル ステートメントは Program クラスの Main メソッドにコンパイルされます。プロジェクト内に 1 つだけ存在でき、ヘルパー関数はローカル関数を使います。

1
2
3
4
5
Console.WriteLine("start");
int total = Sum(1, 2);
Console.WriteLine(total);
static int Sum(int a, int b) => a + b; // ローカル関数

csproj プロジェクト ファイル

csproj はプロジェクト構成です: TargetFramework で対象フレームワーク、ImplicitUsings で暗黙の using、Nullable で null 許容コンテキスト。

1
2
3
4
5
6
7
8
9
<!-- app.csproj -->
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
</Project>

2.変数と定数

型推論、const/readonly、null 許容型、null 合体演算子、nameof、スコープ。

var 型推論

var は初期化子からコンパイラが型を推論します。ローカル変数には使えますが、フィールド/プロパティには使えません。初期化子の型が明確なときは var を使うと簡潔です。

1
2
3
4
5
var count = 42; // int
var name = "Nick"; // string
var list = new List<int>(); // List<int>
var value = 3.14; // double
// var はローカル変数のみ、フィールドは明示型が必須

const と readonly

const はコンパイル時定数(プリミティブ型)、readonly は実行時の読み取り専用フィールド(コンストラクタで代入可能)です。静的定数は static readonly を使うことが多いです。

1
2
3
4
public const int MaxRetry = 3; // コンパイル時定数
public static readonly DateTime Epoch =
new DateTime(1970, 1, 1); // 実行時読み取り専用
// readonly フィールドはコンストラクタで一度だけ代入可能

null 許容型

Nullable<T>(int? など) で値型に null を許可でき、null 許容参照型(? サフィックス + Nullable enable)でコンパイラが null 参照を静的に解析します。

1
2
3
4
int? maybe = null; // 値型の null 許容
int real = maybe ?? 0; // 空合体
string? name = FindName(); // null 許容参照型
if (name is null) return; // コンパイラはこの後 name が非 null と判別

null 合体と null 条件演算子

?? は左辺が null のとき右辺を返し、?. は左辺が null のとき null で短絡評価します。?. をチェーンすると深いアクセスも null 安全に書けます。

1
2
3
4
5
string? s = null;
var result = s ?? "default"; // "default"
int? len = s?.Length; // null(例外を投げない)
var first = s?[0] ?? '-'; // '-'
// ?. と ?? の組み合わせ:null 許容チェーン + フォールバック

nameof 式

nameof はシンボル名を文字列として返し、名前変更時に自動同期されます。引数の検証、プロパティ変更通知、ログ タグなどでよく使われます。

1
2
3
4
5
public void SetAge(int age) {
if (age < 0)
throw new ArgumentOutOfRangeException(nameof(age));
}
var prop = nameof(Person.Name); // "Name"

スコープ

C# は波括弧でブロック スコープを定義します。ローカル変数はフィールドと同名でもよいですが、シャドウは避けてください。using 宣言はリソースのスコープをブロック末尾までに制限します。

1
2
3
4
5
6
int x = 1;
{
int x = 2; // ブロック内で外側ローカルをシャドーイング(可読性低)
Console.WriteLine(x);
}
Console.WriteLine(x); // 1

init とプロパティ初期化

init アクセサーはオブジェクト初期化子での代入を許可し、その後は読み取り専用になります。プロパティ初期化子は既定値を与え、set アクセサーは検証を追加できます。

1
2
3
4
5
6
public class Person {
public string Name { get; init; } = ""; // 初期化時にのみ設定可能
public int Age { get; set; }
}
var p = new Person { Name = "Nick", Age = 30 }; // OK
// p.Name = "X"; // コンパイルエラー:init 後は読み取り専用

分解

タプルと record は分解をサポートします。丸括弧で要素を複数の変数に展開します。Deconstruct メソッドで分解ロジックをカスタマイズできます。

1
2
3
4
5
var (sum, count) = (42, 7); // タプル分解
var (name, age) = GetPerson(); // メソッドがタプルを返す
record Person(string Name, int Age);
var p = new Person("Nick", 30);
var (n, a) = p; // record は分解を標準装備

3.データ型

組み込み型、null 許容、列挙型、構造体、ジェネリック、record、タプル、型変換。

組み込み型

int/long/double/bool などの組み込み型は System 型にマップされます。var は単なる推論で、実行時の型は変わりません。

1
2
3
4
5
6
7
8
int i = 42; // System.Int32
long l = 42L;
double d = 3.14;
float f = 3.14f;
decimal m = 19.99m; // 高精度 10 進数
bool b = true;
char c = 'A';
string s = "hi";

null 許容型

int? は null 許容値型(Nullable<int>)で、HasValue / Value でアクセスします。Nullable enable 下では null 許容参照型がコンパイラによって静的にチェックされます。

1
2
3
4
5
int? x = null;
if (x.HasValue) { int v = x.Value; }
int y = x ?? 0; // 空合体
string? name = null; // null 許容参照型
// Nullable 有効時、name へ直接アクセスすると警告

列挙型

enum は名前を付けた整数定数を定義し、既定では int です。Flags 属性を付けるとビット組み合わせを HasFlag やビット演算で判定できます。

1
2
3
4
5
6
enum Color { Red, Green, Blue } // 既定で 0,1,2
[Flags]
enum Perm { Read = 1, Write = 2, Exec = 4 }
var p = Perm.Read | Perm.Write;
if ((p & Perm.Write) != 0) { } // ビット判定
var c = (Color)1; // Green

構造体

struct は値型です: 代入で全体がコピーされ、スタックに確保され、null にできません(null 許容にするとボックス化されます)。小さく不変なデータに向きます。

1
2
3
4
5
6
struct Point {
public int X, Y;
public Point(int x, int y) { X = x; Y = y; }
}
Point a = new(1, 2);
Point b = a; // 値コピー、b を変えても a に影響しない

クラス(参照型)

class は参照型です: 代入で同じオブジェクトを共有し、GC がメモリを管理し、null にできます。可変状態には class を使います。

1
2
3
4
5
6
class Counter {
public int Value { get; set; }
}
Counter a = new() { Value = 1 };
Counter b = a; // 参照コピー、同一オブジェクトを共有
b.Value = 99; // a.Value も 99

ジェネリック

ジェネリックは型をパラメータ化し、コンパイル時に強く型付けされたコードを生成するため、実行時のボックス化が発生しません。T は型パラメータ、where 句で制約を追加します。

1
2
3
4
5
public class Box<T> {
public T Value { get; set; }
}
var b = new Box<int> { Value = 42 };
// 制約:where T : class, new()

record 型

record(C# 9)は値セマンティクスを持つ参照型です: 値の等価性、ToString、分解、with 式が組み込まれています。DTO や不変データに適しています。

1
2
3
4
5
record Person(string Name, int Age);
var p1 = new Person("Nick", 30);
var p2 = p1 with { Age = 31 }; // 非破壊的変更
bool eq = p1 == p2; // false(値比較)
var (name, age) = p1; // 分解

タプル

タプルは複数の値をまとめて保持し、名前付き要素もサポートします。複数値の返却や簡易なデータ集約に便利で、一時的な用途にはタプル、長期には record を使います。

1
2
3
4
var t = (sum: 42, count: 7); // 名前付きタプル
Console.WriteLine(t.sum);
(int, int) swap((int a, int b) t) => (t.b, t.a);
var (a, b) = (1, 2);

型変換

is は安全な型チェック、as は安全な変換(失敗時 null)、(T) は強制変換(失敗時例外)、Convert/Parse で明示的に解析します。

1
2
3
4
5
6
object o = "hello";
if (o is string s) { } // パターンマッチ + 変換
var str = o as string; // null または string
// var bad = (int)o; // InvalidCastException をスロー
int n = int.Parse("42"); // 明示的解析
bool ok = int.TryParse("42", out int v); // 安全な解析

パターン マッチング

is / switch はパターン マッチングに使います: 型パターン、プロパティ パターン、位置パターン。大量の if とキャストを置き換え、意図を明確に表現できます。

1
2
3
4
5
6
7
string Describe(object o) => o switch {
int n when n > 0 => "positive",
int n => "integer",
string s when s.Length > 3 => "long string",
null => "null",
_ => "other", // フォールバック
};

4.参照と配列

値型と参照型、配列、Span、インデックスと範囲、ref/in/out、unsafe ポインタ。

値型 vs 参照型

値型(struct / 列挙型 / プリミティブ型)は代入でコピーされ、参照型(class / インターフェース)は代入で同じオブジェクトを共有します。これが C# の最も重要なメンタル モデルです。

1
2
3
4
5
6
7
8
// struct は値コピー:b は独立したコピー
Point a = new(1, 2);
Point b = a;
b.X = 99; // a.X はまだ 1
// class は参照を共有
var c1 = new Counter();
var c2 = c1;
c2.Value = 5; // c1.Value も 5

配列

配列は固定長の参照型で、[] でインデックス アクセスします。多次元配列とジャグ配列は異なります: [,] は矩形、[][] はジャグ。配列では Count ではなく Length を使います。

1
2
3
4
5
6
int[] arr = { 1, 2, 3 };
int first = arr[0];
arr[2] = 99;
int len = arr.Length; // 3
int[,] rect = new int[2, 3]; // 2 次元の矩形配列
int[][] jag = new int[2][]; // ジャグ配列

インデックスと範囲

^ は末尾からのインデックス(^1 で最後)、.. は範囲を表します(1..^1 で先頭と末尾を除く)。配列 / List のサブセットを切り出すモダンな書き方です。

1
2
3
4
5
int[] arr = { 1, 2, 3, 4, 5 };
int last = arr[^1]; // 5
int secondLast = arr[^2]; // 4
int[] mid = arr[1..^1]; // { 2, 3, 4 }
int[] tail = arr[2..]; // { 3, 4, 5 }

Span とメモリ

Span<T> は任意の連続メモリへの読み取りビューで、割り当てなしでスライスでき、高性能データ処理の中核です。スタック/ヒープのどちらのメモリも指せます。

1
2
3
4
5
int[] arr = { 1, 2, 3, 4 };
Span<int> s = arr;
Span<int> part = s[1..3]; // スライス、コピーなし
part[0] = 99; // 元の配列を変更
// スタックメモリも可能:Span<int> st = stackalloc int[4];

ref / in / out パラメータ

ref は読み書き可能な参照渡し、in は読み取り専用の参照渡し、out は戻り値用(呼び出し前に初期化不要)です。参照渡しで値型のコピーを回避できます。

1
2
3
4
5
6
7
void Increment(ref int x) => x++;
void Init(out int x) => x = 42;
int ReadOnly(in int x) => x;
int v = 1;
Increment(ref v); // v = 2
Init(out v); // v = 42
ReadOnly(in v); // 読み取り専用参照

unsafe ポインタ

unsafe コンテキストでは本物のポインタ(int* など)を使えます。csproj で AllowUnsafeBlocks を有効化する必要があります。ネイティブ相互運用時のみ使い、通常のコードでは避けてください。

1
2
3
4
5
6
unsafe {
int x = 42;
int* p = &x; // アドレス取得
Console.WriteLine(*p); // 逆参照
}
// csproj: <AllowUnsafeBlocks>true</AllowUnsafeBlocks>

Memory とバッファ

Memory<T> は Span のヒープ セーフ版で、フィールドに保存でき async をまたいで使えます。ReadOnlyMemory は読み取り専用ビューです。非同期バッファ処理に使われます。

1
2
3
4
byte[] data = GetBytes();
Memory<byte> mem = data; // async をまたいで保持可能
ReadOnlyMemory<byte> rom = mem; // 読み取り専用ビュー
// Span はフィールド/await 不可、Memory は可能

コピー セマンティクス

値型は既定で値渡し(大きな struct はコピー コストが発生)、オブジェクト(参照)は参照渡しです。オブジェクトをその場で変更したい場合は ref を使いません。

1
2
3
4
5
struct Big { public long A, B, C, D; }
void PassByValue(Big b) { } // struct 全体をコピー
void PassByRef(ref Big b) { } // 参照渡し、ゼロコピー
var x = new Big();
PassByRef(ref x); // 元のオブジェクトが変更される

5.制御フロー

if/else、for/foreach/while、switch 式、break/continue、throw 式。

if / else

if / else は条件で分岐します。C# では比較に ==、短絡評価に && と || を使います。単一文では波括弧を省略できますが、残すことを推奨します。

1
2
3
4
5
6
7
if (score >= 90) {
grade = "A";
} else if (score >= 60) {
grade = "B";
} else {
grade = "F";
}

for ループ

古典的な for: 初期化、条件、増分。インデックスが必要なときに使います。多くの場合は foreach の方が安全です。

1
2
3
4
for (int i = 0; i < 10; i++) {
Console.WriteLine(i); // 0..9
}
for (int i = arr.Length - 1; i >= 0; i--) { } // 逆順

foreach 反復

foreach はインデックスなしでコレクションを反復し、コンパイル時に安全です。var や LINQ と組み合わせてデータをチェーン処理するのが最も一般的です。

1
2
3
4
5
foreach (var item in items) {
Console.WriteLine(item);
}
// 添字が必要なら for か Enumerable.Select
foreach (var (i, item) in items.Select((x, i) => (x, i))) { }

while / do-while

while は判定してから実行、do-while は少なくとも 1 回実行します。ストリームを EOF まで読むなど、ループ回数が不明な場面に適しています。

1
2
3
4
5
6
int i = 0;
while (i < 5) { i++; } // 先に判定
string? line;
do {
line = Console.ReadLine();
} while (line != null); // 最低 1 回は実行

switch 式

switch 式(C# 8)は => で値を返し、長い if-else チェーンを置き換えます。型パターンと when ガードの組み合わせは強力です。

1
2
3
4
5
6
7
8
9
10
11
string Describe(int n) => n switch {
0 => "zero",
1 => "one",
_ => "other", // フォールバック
};
// 型パターン:オブジェクトを型で分岐
string Type(object o) => o switch {
int => "int",
string => "string",
_ => "other",
};

break と continue

continue は現在の反復をスキップして次へ、break はループを抜け、return はメソッドを抜けます。多重ループを抜けるには goto を使います。

1
2
3
4
5
6
7
for (int i = 0; i < 10; i++) {
if (i % 2 == 0) continue; // 偶数をスキップ
if (i > 7) break; // ループを抜ける
}
// 二重ループを一気に抜ける
goto Exit;
Exit: ;

三項演算子

cond ? a : b で if / else の代入を 1 行で書けます。分岐の式は互換性のある型(共通型に変換可能)でなければなりません。

1
2
var grade = score >= 60 ? "pass" : "fail";
var label = score switch { >= 90 => "A", _ => "other" };

throw 式

throw は式として ?? や ?: の右辺、メソッド本体 1 行で使うことができます。ArgumentNullException を投げて引数検証を簡潔に書けます。

1
2
3
string s = name ?? throw new ArgumentNullException(nameof(name));
int v = dict.TryGetValue(k, out var val) ? val :
throw new KeyNotFoundException(k);

6.関数とメソッド

メソッド シグネチャ、パラメータ渡し、オーバーロード、ローカル関数、ラムダ式、式本体メンバー。

メソッド定義

メソッド = 戻り型 + 名前 + パラメータ リスト + 本体です。void は戻り値なしを表します。アクセス修飾子で可視性を制御します。

1
2
3
4
5
6
7
public int Add(int a, int b) {
return a + b;
}
public void Say(string msg) {
Console.WriteLine(msg); // void は戻り値なし
}
private int twice = 0; // フィールド

パラメータ渡し

既定では値渡しです: メソッド内の変更は呼び出し元の変数に影響しません。オブジェクトは参照のコピーが渡されるため、メンバーを変更すると元のオブジェクトに影響します。

1
2
3
4
5
6
void Set(int x) { x = 99; } // 値渡し:呼び出し元は不変
void Mutate(List<int> l) { l.Add(1); } // 参照:呼び出し元にも影響
var n = 1;
Set(n); // n はまだ 1
var list = new List<int>();
Mutate(list); // list は [1] になる

オプション引数と名前付き引数

オプション引数は既定値を持ち(パラメータ リストの末尾に置く必要があります)、名前付き引数で名前を基に渡し、中間のオプション引数をスキップできます。

1
2
3
4
5
6
void Greet(string name, string prefix = "Hello", int times = 1) {
Console.WriteLine($"{prefix}, {name}");
}
Greet("Nick"); // デフォルトを使う
Greet("Nick", "Hi");
Greet("Nick", times: 3); // 名前付き引数で prefix をスキップ

ref / out / in

ref は読み書き可能な参照渡し、out は出力専用(呼び出し前に初期化不要)、in は読み取り専用の参照渡し(大きな値型のコピーを回避)。

1
2
3
4
5
6
7
void Ref(ref int x) => x++;
void Out(out int x) => x = 42;
void In(in int x) { /* 読み取り専用 */ }
int v = 1;
Ref(ref v); // v = 2
Out(out v); // v = 42
In(in v); // ゼロコピー読み取り専用

メソッド オーバーロード

オーバーロード = 同じ名前のメソッドでパラメータ リストが異なる(数 / 型)。コンパイラが引数に基づき最適なバージョンを選びます。戻り型はオーバーロード解決に参加しません。

1
2
3
4
int Parse(string s) => int.Parse(s);
int Parse(int x) => x; // オーバーロード:引数型が異なる
long Parse(string s, int radix) => // オーバーロード:引数の数が異なる
Convert.ToInt64(s, radix);

式本体メンバー

メソッド / プロパティ / コンストラクタが単一式の場合、=> で簡潔に書けます。ラムダと似ていますが、式本体メンバーは本物のメンバーです。

1
2
3
4
5
public int Square(int x) => x * x;
public string Name => "Nick"; // 読み取り専用プロパティ
public void Reset() => Count = 0;
// コンストラクタにも使える:
class Box { public Box(string s) => Label = s; string Label { get; } }

ローカル関数

メソッド内で定義した関数は外側の変数(クロージャ)へアクセスでき、再帰ヘルパーや反復子内部の処理でよく使われます。

1
2
3
4
5
int Factorial(int n) {
int Helper(int x) => x <= 1 ? 1 : x * Helper(x - 1);
return Helper(n);
}
// Helper はこのメソッド内でのみ使える

ラムダ式

匿名関数: 引数 => 式。デリゲート / Func / Action と組み合わせて使い、LINQ の中心的な構文です。

1
2
3
4
5
6
7
8
Func<int, int> square = x => x * x;
Func<int, int, int> add = (a, b) => a + b;
Action<string> print = msg => Console.WriteLine(msg);
// 複数行ボディ:
Func<int, int> twice = (x) => {
int r = x * 2;
return r;
};

デリゲート

デリゲートはメソッドの型です: シグネチャを宣言し、+= で複数のメソッドを購読、デリゲートを呼び出すと順番に発火します。イベントはデリゲートに基づきます。

1
2
3
4
5
delegate void Notify(string msg);
void Log(string m) => Console.WriteLine(m);
Notify n = Log; // デリゲートがメソッドを指す
n += m => Console.WriteLine("!" + m);
n("hi"); // 登録された全メソッドを順に呼ぶ

7.文字列

文字列リテラル、補間、一般的メソッド、可変 StringBuilder、書式設定。

文字列リテラル

二重引用符の通常の文字列、@ の逐語的文字列(エスケープ処理なし)、$ の補間文字列、$$ の複合リテラル(C# 11)。

1
2
3
string s = "Line\n\tIndent"; // エスケープ
string raw = @"C:\Program Files\"; // 逐語的:\ をエスケープしない
string path = "C:\\Program Files\\"; // 等価な通常の書き方

文字列補間

$ 接頭辞は {} 内の式の結果を文字列にインライン展開します。書式と配置も指定でき、+ 連結より読みやすい書き方です。

1
2
3
4
5
int age = 30;
string name = "Nick";
var msg = $"{name} is {age} years old";
var pad = $"{name,10}"; // 右寄せ幅 10
var money = $"{age:C}"; // 通貨フォーマット

連結と比較

+ で連結、string.Concat で一括連結、string.Join で区切り文字付き連結。文字列の等価比較は ==(値比較)で行います。

1
2
3
4
string a = "foo" + "bar"; // "foobar"
string all = string.Join(", ", names); // "a, b, c"
bool eq = a == "foobar"; // true(値比較)
int cmp = string.Compare("a", "b"); // 辞書順で比較

一般的なメソッド

Length、Substring、Contains、StartsWith、IndexOf、Replace、Trim、Split、ToUpper/ToLower はほとんどの文字列処理をカバーします。

1
2
3
4
5
6
string s = " Hello, World ";
int len = s.Length; // 14
var sub = s.Substring(7, 5); // "World"
bool has = s.Contains("World"); // true
var rep = s.Replace("World", "C#");
var words = s.Trim().Split(","); // ["Hello", " World"]

StringBuilder

大量の連結には StringBuilder を使い、文字列生成の繰り返しを避けます(文字列は不変で、+ は毎回新しいオブジェクトを生成)。ループ内での連結で特に顕著です。

1
2
3
4
5
var sb = new StringBuilder();
for (int i = 0; i < 100; i++) {
sb.Append(i).Append(",");
}
string result = sb.ToString(); // 一括で文字列に変換

文字 char

char は UTF-16 の単一文字です。char.IsDigit / IsLetter / IsWhiteSpace で種類を判定します。string は不変、char は反復可能。

1
2
3
4
5
6
char c = 'A';
bool digit = char.IsDigit(c); // false
bool upper = char.IsUpper(c); // true
foreach (char ch in "Hi") {
Console.WriteLine(ch); // 'H', 'i'
}

書式設定

string.Format / $ 補間では書式指定子を使います: D 整数、F 小数、C 通貨、P パーセント、X 16 進数。

1
2
3
4
5
double d = 1234.567;
Console.WriteLine($"{d:F2}"); // 1234.57
Console.WriteLine($"{d:C}"); // ¥1,234.57(ロケール依存)
Console.WriteLine($"{42:D5}"); // 00042
Console.WriteLine($"{255:X}"); // FF

文字列の解析

Parse は文字列を数値に変換し(失敗で例外)、TryParse は安全(bool と out で結果を返します)。TryParse を優先します。

1
2
3
4
int n = int.Parse("42"); // 例外を投げる可能性
bool ok = int.TryParse("42", out int v); // 安全
if (ok) Console.WriteLine(v);
// 他に:int.Parse("ff", NumberStyles.HexNumber)

8.コレクションと LINQ

List/Dictionary/HashSet などの一般的なコレクション、IEnumerable、LINQ のチェーン クエリ。

List 動的配列

List<T> は可変長配列で、Add / Insert / Remove / IndexOf があり、インデックス アクセスは O(1)。foreach で反復します。

1
2
3
4
5
6
var list = new List<int> { 1, 2, 3 };
list.Add(4);
list.Insert(0, 0); // [0,1,2,3,4]
list.Remove(2);
int count = list.Count; // Length の代わりに Count
bool any = list.Contains(4);

Dictionary 辞書

Dictionary<K,V> はキーから値へのマップで、キーで O(1) ルックアップ。TryGetValue で安全に取得し、KeyValuePair を反復します。

1
2
3
4
5
6
7
8
var dict = new Dictionary<string, int>();
dict["Nick"] = 30;
if (dict.TryGetValue("Nick", out int age)) {
Console.WriteLine(age);
}
foreach (var kv in dict) {
Console.WriteLine($"{kv.Key}: {kv.Value}");
}

HashSet 集合

HashSet<T> は重複なしで O(1) の Contains 判定。UnionWith / IntersectWith / ExceptWith で集合演算(重複排除・マージ)を行います。

1
2
3
4
5
6
var set = new HashSet<int> { 1, 2, 3 };
set.Add(3); // 既に存在、効果なし
bool has = set.Contains(2);
set.UnionWith(new[] { 3, 4 }); // {1,2,3,4}
var common = new HashSet<int> { 2 };
common.IntersectWith(set); // {2}

Queue と Stack

Queue<T> は FIFO(Enqueue / Dequeue)、Stack<T> は LIFO(Push / Pop)。タスク キューやアンドゥ スタックの処理に使います。

1
2
3
4
5
6
var q = new Queue<int>();
q.Enqueue(1); q.Enqueue(2);
int next = q.Dequeue(); // 1
var s = new Stack<int>();
s.Push(1); s.Push(2);
int top = s.Pop(); // 2

IEnumerable と遅延

IEnumerable<T> は読み取り専用のシーケンス インターフェースで、LINQ はこれに基づいて遅延評価を行います(反復時に計算)。配列 / List / Dictionary がすべて実装します。

1
2
3
4
IEnumerable<int> nums = GetNums(); // 遅延:まだ計算されない
int sum = nums.Where(n => n > 0)
.Sum(); // 走査したときに評価される
// ToList()/ToArray() で具体化し、即座に全て実行

LINQ フィルタと射影

Where はフィルタ、Select は射影(マップ)、OrderBy はソート、Distinct は重複除去。メソッド チェーンは読みやすく遅延評価です。

1
2
3
4
5
var adults = people
.Where(p => p.Age >= 18) // フィルタ
.OrderByDescending(p => p.Age) // ソート
.Select(p => p.Name) // 射影
.ToList(); // 具体化

LINQ 集計

Count / Sum / Average / Min / Max で集計し、Any / All で判定、First / Single で要素を取得します(マッチなしで例外、FirstOrDefault は既定値を返す)。

1
2
3
4
5
6
7
int[] nums = { 1, 2, 3, 4 };
int sum = nums.Sum(); // 10
double avg = nums.Average(); // 2.5
bool anyEven = nums.Any(n => n % 2 == 0); // true
bool allPos = nums.All(n => n > 0); // true
int first = nums.First(n => n > 2); // 3
int? maybe = nums.FirstOrDefault(n => n > 9); // null

GroupBy グループ化

GroupBy はキーでグループ化し、IGrouping のシーケンスを生成します。統計(カテゴリ別のカウント・合計)によく使われます。

1
2
3
4
5
6
7
8
var stats = orders
.GroupBy(o => o.Region)
.Select(g => new {
Region = g.Key,
Total = g.Sum(o => o.Amount),
Count = g.Count(),
})
.ToList();

LINQ クエリ構文

from / where / orderby / select のクエリ構文はメソッド チェーンの宣言的な書き方で、コンパイル後は等価です。可読性の好みの問題です。

1
2
3
4
5
6
7
8
var adults = from p in people
where p.Age >= 18
orderby p.Age descending
select p.Name;
// 等価なメソッドチェーン:
var same = people.Where(p => p.Age >= 18)
.OrderByDescending(p => p.Age)
.Select(p => p.Name);

9.メモリとリソース管理

GC による自動回収、IDisposable と using によるアンマネージ リソース解放、弱参照、オブジェクト プール。

GC ガベージ コレクション

C# のメモリは GC が自動管理します: ヒープ上のオブジェクトが参照されなくなると回収されます。世代別 GC(0 / 1 / 2)がパフォーマンスを最適化します。

1
2
3
var obj = new object(); // ヒープ割り当て
// 参照がなくなると GC が自動回収、手動 free 不要
GC.Collect(); // 強制回収(通常は手動で呼ばない)

IDisposable インターフェース

アンマネージ リソース(ファイル / ネットワーク / DB 接続)を保持するクラスは IDisposable を実装し、Dispose でリソースを解放します。

1
2
3
4
5
6
7
class FileWriter : IDisposable {
private StreamWriter? _sw;
public void Dispose() {
_sw?.Dispose(); // アンマネージリソースを解放
}
}
using (var w = new FileWriter()) { } // 自動で Dispose

using 文と宣言

using はスコープ終了時に Dispose を保証します(finally セマンティクス)。using 宣言(C# 8)はブロック終了時にリソースを自動解放します。

1
2
3
4
5
using var reader = new StreamReader("file.txt");
string line = reader.ReadLine();
// スコープ終了時に自動 Dispose、明示不要
// 等価な従来の書き方:
using (var r = new StreamReader("file.txt")) { }

ファイナライザ

デストラクタ ~Class() はファイナライザです: GC による回収前に呼ばれますが、タイミングは保証されません。アンマネージ リソースの解放のみに使用し、通常は Dispose パターンを採ります。

1
2
3
4
5
6
class Resource {
~Resource() {
// GC の前に呼ばれるがタイミングは不確定——頼るな
// IDisposable を実装して明示的に解放すべき
}
}

弱参照

WeakReference は GC によるターゲットの回収を妨げず、キャッシュ(重いオブジェクトの辞書キャッシュ、回収を許容する)に使われます。Target はいつでも null になり得ます。

1
2
3
4
5
var weak = new WeakReference(new byte[1024]);
if (weak.IsAlive) {
var data = (byte[])weak.Target!; // 既に回収されている可能性
}
// GC 後は IsAlive == false になり得る

オブジェクト プールと ArrayPool

大きな配列を頻繁に割り当てる場合は ArrayPool でバッファを再利用し、GC の負荷を減らします。ArrayPool<T>.Shared.Rent と Return はペアで使います。

1
2
3
4
5
6
7
byte[] buf = ArrayPool<byte>.Shared.Rent(1024);
try {
// buf を使用
} finally {
ArrayPool<byte>.Shared.Return(buf);
}
// Rent の配列は要求より大きい可能性、使い終わったら必ず Return

stackalloc スタック割り当て

stackalloc はスタックにメモリを割り当て、高速で GC を発生させません。小さな一時バッファに適しますが、スタック領域は有限なので注意が必要です。

1
2
3
4
5
6
int length = 4;
// stackalloc はスタック上に割り当て、高速で GC を発生させない
Span<int> buffer = stackalloc int[length];
buffer[0] = 42;
foreach (var n in buffer) { Console.WriteLine(n); }
// スタック領域は限られているので、小さな一時バッファ向け

GC メモリ圧迫

大量のネイティブ メモリを割り当てる場合、GC.AddMemoryPressure で GC に通知し、マネージ オブジェクトを速やかに回収させてメモリ使用量の失控を防ぎます。

1
2
3
4
5
6
7
8
9
10
var size = 64 * 1024 * 1024; // 64 MB
IntPtr buffer = System.Runtime.InteropServices.Marshal.AllocHGlobal(size);
GC.AddMemoryPressure(size); // GC にネイティブメモリの圧力を通知
try {
// ネイティブバッファを使用(例:1 バイト書き込む)
System.Runtime.InteropServices.Marshal.WriteByte(buffer, 0, 1);
} finally {
System.Runtime.InteropServices.Marshal.FreeHGlobal(buffer);
GC.RemoveMemoryPressure(size); // 解放後に圧力を除去
}

10.オブジェクト指向

クラス、プロパティ、コンストラクタ、継承、多態性、インターフェース、抽象クラス、アクセス修飾子。

クラスとオブジェクト

class はデータ型(参照型)を定義します。フィールドが状態を保持し、プロパティがアクセスを制御し、メソッドが振る舞いを定義し、コンストラクタが初期化します。

1
2
3
4
5
6
7
8
9
public class Person {
public string Name { get; set; } = "";
public int Age { get; set; }
public Person(string name, int age) {
Name = name; Age = age;
}
public string Greet() => $"Hi, {Name}";
}
var p = new Person("Nick", 30);

プロパティ

プロパティはフィールドへの安全なアクセサです: get で読み取り、set で書き込み。アクセス修飾子と検証ロジックを追加できます。コンパイラがバッキング フィールドを生成します。

1
2
3
4
5
6
7
8
9
10
private int _age;
public int Age {
get => _age;
set {
if (value < 0) throw new ArgumentOutOfRangeException();
_age = value;
}
}
// 自動プロパティ:
public string Name { get; set; } = ""

継承

C# は単一継承です: class は : で 1 つの基底クラスを継承します。派生クラスは基底クラスの is-a です。private メンバーは継承されず、protected はアクセス可能です。

1
2
3
4
5
6
7
8
9
public class Animal {
public string Name { get; set; } = "";
public virtual void Speak() => Console.WriteLine("...");
}
public class Dog : Animal {
public override void Speak() => Console.WriteLine("Woof");
}
Animal a = new Dog();
a.Speak(); // "Woof"(ポリモーフィズム)

多態性

virtual と override で多態性を実現します: 基底クラスの参照から呼び出されたメソッドは実際の型にディスパッチされます。メソッドは virtual でないとオーバーライドできません。

1
2
3
4
5
6
7
8
9
public class Shape {
public virtual double Area() => 0;
}
public class Circle : Shape {
public double R { get; set; }
public override double Area() => Math.PI * R * R;
}
Shape s = new Circle { R = 2 };
double area = s.Area(); // 12.57(実際の型に応じて)

抽象クラス

abstract クラスはインスタンス化できず、abstract メソッド(派生クラスが必ず実装)を保有できます。抽象メソッドには本体がありません。テンプレート基底クラスに使います。

1
2
3
4
5
6
7
8
9
public abstract class Shape {
public abstract double Area(); // 実装なし
public void Describe() =>
Console.WriteLine($"Area: {Area()}");
}
public class Square : Shape {
public double Side { get; set; }
public override double Area() => Side * Side;
}

インターフェース

interface は契約を定義します: メンバーは実装を持ちません。実装クラスはすべて提供します。C# は複数の interface 実装をサポートします(多重継承の代替)。

1
2
3
4
5
6
7
8
public interface ILogger {
void Log(string msg);
}
public class ConsoleLogger : ILogger {
public void Log(string msg) => Console.WriteLine(msg);
}
ILogger logger = new ConsoleLogger();
logger.Log("hi"); // インターフェース指向プログラミング

アクセス修飾子

public は公開、private は非公開、protected は派生クラスから可視、internal は同一アセンブリ内で可視。クラスは既定で private、メンバーは既定で private。

1
2
3
4
5
6
public class Account {
public decimal Balance { get; private set; } // 外部は読み取り、内部は書き込み
private int _transactions; // このクラス内のみ
protected void Audit() { } // 派生クラスのみ
}
// internal:アセンブリ内で可視(トップレベルクラスの既定)

sealed と object メソッド

sealed クラスは継承不可、sealed override メソッドはそれ以上オーバーライドできません。すべてのクラスは暗黙的に object(ToString / Equals / GetHashCode)を継承します。

1
2
3
4
5
6
7
8
public sealed class Config { } // 継承禁止
public class Base {
public virtual void Run() { }
}
public class Child : Base {
public override void Run() { } // オーバーライド可能
}
// Child で Run を sealed override にすると孫クラスで再オーバーライド不可

静的クラスと拡張メソッド

static class は静的メンバーのみを持ち、インスタンス化できません。拡張メソッドは静的クラス内の静的メソッドで、this パラメータによりインスタンスから呼び出せるようになります。

1
2
3
4
5
6
public static class StringExt {
public static bool IsEmpty(this string s) =>
string.IsNullOrEmpty(s);
}
string s = "";
bool empty = s.IsEmpty(); // インスタンス構文で拡張メソッドを呼ぶ

11.例外処理

try/catch/finally、例外型、カスタム例外、例外のコスト、ベスト プラクティス。

try / catch / finally

try には失敗し得るコードを置き、catch で捕捉して処理し、finally は成否に関わらず実行されます(リソースのクリーンアップ)。例外は上位へ伝播します。

1
2
3
4
5
6
7
try {
int.Parse("abc");
} catch (FormatException ex) {
Console.WriteLine(ex.Message); // エラー処理
} finally {
Console.WriteLine("cleanup"); // 常に実行される
}

複数の catch と例外フィルター

複数の catch は型でマッチし、具体的な例外を先に書きます。when でフィルタ条件を指定できます。変数を宣言しない catch は例外オブジェクトを無視します。

1
2
3
4
5
6
try {
Process();
} catch (FileNotFoundException ex) when (ex.FileName == "cfg")
{ /* フィルタ:ファイル名が一致する場合のみ */ }
catch (IOException ex) { /* より広い例外 */ }
catch { /* すべて捕捉 */ }

例外のスロー

throw new は例外を投げ、throw; は引数なしでそのまま再スローします(スタックを保持)。catch 内で new してから throw するとスタックがリセットされます。

1
2
3
4
5
6
throw new ArgumentException("invalid value", nameof(value));
try { ... }
catch (Exception ex) {
// throw; // スタックを保持
// throw ex; // スタックをリセット(情報が失われるので避ける)
}

カスタム例外

カスタム例外は Exception を継承し(慣例として Exception 接尾辞を付ける)、コンストラクタを提供し、内部例外を InnerException として保持します。

1
2
3
4
5
6
7
public class ConfigException : Exception {
public ConfigException() { }
public ConfigException(string message) : base(message) { }
public ConfigException(string message, Exception inner)
: base(message, inner) { }
}
throw new ConfigException("bad config");

finally クリーンアップ

finally は try が例外を出すかどうかに関わらず、リソース解放 / 状態復元を保証します。return も finally を先に実行します。using はそのシンタックス シュガーです。

1
2
3
4
5
6
7
try {
lockObj.Enter();
DoWork();
} finally {
lockObj.Exit(); // 成否に関わらずロック解除
}
// return は finally の後に返る、finally は必ず実行

例外のコスト

例外のキャッチにはコストがかかるため、制御フローとして使ってはいけません。予期できるエラーはリターン コード / TryXxx / Result パターンで処理します。

1
2
3
4
5
6
// 遅い:例外でフロー制御するのは避ける
bool ok1;
try { int.Parse(s); ok1 = true; }
catch { ok1 = false; }
// 速い:TryParse が結果を直接返す
bool ok2 = int.TryParse(s, out _);

グローバル例外処理

トップ レベルで try / catch を使って未処理例外を捕捉し、ログに記録します。ASP.NET ではミドルウェア(UseExceptionHandler)で一括処理します。

1
2
3
4
5
6
7
try {
await RunAsync();
} catch (Exception ex) {
Log(ex); // 記録
Environment.ExitCode = 1; // 失敗をマーク
}
// ASP.NET Core:app.UseExceptionHandler(...)

InnerException チェーン

例外をキャッチした後、新しい例外を投げ、元の例外を InnerException として渡すことで、完全なエラー チェーンを保持でき、ログ調査に有用です。

1
2
3
4
5
6
7
try {
LoadConfig();
} catch (FileNotFoundException ex) {
// 例外をラップし、元の原因を保持
throw new InvalidOperationException("配置缺失", ex);
}
// 調査:ex.InnerException で根本原因まで遡れる

12.ファイルと I/O

File/Directory/Path の静的ユーティリティ、Stream の読み書き、非同期 I/O、バイナリ処理。

テキスト ファイルの読み書き

File.ReadAllText / WriteAllText は小さなファイルを一度に読み書きし、ReadAllLines は行ごとの配列を返します。大きなファイルには StreamReader を使います。

1
2
3
4
5
string content = File.ReadAllText("in.txt");
File.WriteAllText("out.txt", content);
string[] lines = File.ReadAllLines("in.txt");
File.WriteAllLines("out.txt", lines);
// エンコーディング:File.ReadAllText(path, Encoding.UTF8)

StreamReader 行ごと

大きなファイルは行ごとにストリーミング読み込みし、メモリを消費しません。StreamReader.ReadLine は null(EOF)までループします。

1
2
3
4
5
6
using var reader = new StreamReader("big.log");
string? line;
while ((line = reader.ReadLine()) != null) {
Process(line);
}
// using により読み終わると自動解放

ディレクトリ操作

Directory はディレクトリの作成 / 削除 / 列挙、Directory.GetFiles / EnumerateFiles はファイル検索。再帰列挙には SearchOption を使います。

1
2
3
4
5
6
Directory.CreateDirectory("data");
if (Directory.Exists("data")) { }
string[] files = Directory.GetFiles("data", "*.txt");
// 再帰:
var all = Directory.EnumerateFiles("data", "*",
SearchOption.AllDirectories);

Path パス処理

Path はクロス プラットフォームのパス結合 / 解析を行います: Combine、GetExtension、GetFileName、ChangeExtension。パスの区切り文字を手書きしないでください。

1
2
3
4
string dir = Path.Combine("data", "files");
string ext = Path.GetExtension("a.txt"); // ".txt"
string name = Path.GetFileName("/a/b.txt"); // "b.txt"
string changed = Path.ChangeExtension("a.txt", ".md");

非同期 I/O

async / await での I/O はスレッドをブロックしません: ReadAllTextAsync / WriteAllTextAsync / Stream 系メソッド。UI / サーバーの並行シナリオでは必須です。

1
2
3
4
5
6
string text = await File.ReadAllTextAsync("in.txt");
await File.WriteAllTextAsync("out.txt", text);
// async Main / async メソッド内で使用可
using var reader = new StreamReader("big.log");
string? line;
while ((line = await reader.ReadLineAsync()) != null) { }

バイナリ読み書き

BinaryWriter / BinaryReader は型ごとに読み書きし、MemoryStream はメモリ内ストリームです。ネットワークやファイルのバイナリ プロトコルで一般的に使われます。

1
2
3
4
5
6
7
using var ms = new MemoryStream();
using var w = new BinaryWriter(ms);
w.Write(42); w.Write("data");
ms.Position = 0;
using var r = new BinaryReader(ms);
int n = r.ReadInt32();
string s = r.ReadString();

コンソール I/O

Console.ReadLine は 1 行読み込み、ReadKey はキー入力を読み、WriteLine は出力します。Console.In / Out をリダイレクトしてテストできます。

1
2
3
4
5
Console.Write("Name: ");
string? name = Console.ReadLine();
var key = Console.ReadKey(); // 単一キーを読む
Console.WriteLine($"Hello {name} ({key.KeyChar})");
// 入力リダイレクト:dotnet run < input.txt

ファイル情報

FileInfo / DirectoryInfo はファイルのメタデータとメソッドを提供し、File.Exists は存在を確認し、GetCreationTime はタイムスタンプを返します。

1
2
3
4
5
6
7
var fi = new FileInfo("a.txt");
if (fi.Exists) {
long bytes = fi.Length;
DateTime created = fi.CreationTime;
fi.CopyTo("b.txt");
}
bool ok = File.Exists("a.txt");

13.よくある落とし穴(FAQ)

C# 開発者が最も踏みやすい罠: 値型 vs 参照、文字列の等価性、LINQ の遅延、async/await のデッドロックなど。

文字列 == と Equal

C# の string == は値による比較を行います(Java と異なります)。ただし == は序数比較で、Equals は StringComparison を指定して大文字小文字を無視するなどの比較ができます。

1
2
3
4
5
// BAD:既定の序数比較に依存、大文字小文字で想定外の結果に
bool bad = a == "nick";
// GOOD:比較ルールを明示
bool good = a.Equals("nick", StringComparison.OrdinalIgnoreCase);

値渡し vs 参照渡し

メソッド パラメータは既定で値渡しです: オブジェクトは参照のコピーが渡されるため、メンバーを変更すると元のオブジェクトに影響します。値型はコピーされます。これを忘れるのはよくあるバグです。

1
2
3
4
5
6
7
8
9
// BAD:List が値渡しで変更されないと思い込む
void Bad(List<int> l) => l.Add(1);
var list = new List<int>();
Bad(list); // 実は list が変更されている
// GOOD:参照渡しを意識するか、ref で意図を明示
void Good(ref List<int> l) => l.Add(1);
var list2 = new List<int>();
Good(ref list2);

LINQ 遅延評価

LINQ クエリは遅延評価です: 列挙して初めて計算されます。IQueryable / IEnumerable を外へ渡してから列挙すると、副作用や古いデータが混じる可能性があります。

1
2
3
4
5
6
// BAD:LINQ は遅延評価、list を変えるとクエリ結果が変わる
var q = list.Where(n => n > 0);
list.Add(5); // q の結果が変化
// GOOD:具体化してスナップショット
var snapshot = list.Where(n => n > 0).ToList();

async void とデッドロック

イベント ハンドラーは async void でも構いませんが、通常のメソッドは Task を返す必要があります。sync コンテキスト(UI / WinForms)で .Result / .Wait() を使うとデッドロックします。

1
2
3
4
5
6
// BAD:async void メソッド、例外を捕捉できずテストが脆い
async void Bad() { await Task.Delay(10); }
// GOOD:Task を返し、await で例外を伝播
async Task Good() { await Task.Delay(10); }
// UI では .Result でブロックせず await を使う

例外の飲み込みと再スロー

catch 後に処理しないと例外を握り潰し、throw ex はスタックをリセットして元の例外チェーンを失います。ログには必ず InnerException を残してください。

1
2
3
4
5
6
7
8
// BAD:例外を握りつぶすとデバッグ不能
catch (Exception ex) { }
// GOOD:記録するかそのまま再スロー
catch (Exception ex) {
_logger.LogError(ex, "failed");
throw; // スタックを保持
}

未解放リソース

Stream / HttpClient / DB 接続を解放しないとリソースがリークします。IDisposable は必ず using を使うか、使い終わったら Dispose します。

1
2
3
4
5
6
7
// BAD:Stream が解放されない
var sr = new StreamReader("a.txt");
string t = sr.ReadToEnd();
// GOOD:using で解放を保証
using var good = new StreamReader("a.txt");
string t2 = good.ReadToEnd();

浮動小数点の比較

float / double は 2 進表現のため精度が不正確で、直接 == で比較すると想定外の結果になります。イプシロン許容差を使うか、正確な演算には decimal を使います。

1
2
3
4
5
6
// BAD:浮動小数点の完全一致は当てにならない
bool bad = (0.1 + 0.2) == 0.3; // false
// GOOD:許容誤差で比較
bool good = Math.Abs((0.1 + 0.2) - 0.3) < 1e-9;
// 金額は decimal:0.1m + 0.2m == 0.3m は true

反復中のコレクション変更

foreach 内でコレクションの Add / Remove を行うと InvalidOperationException が投げられます。削除対象の要素を収集し、反復後に削除してください。

1
2
3
4
5
6
7
8
// BAD:foreach 中の Remove は例外を投げる
foreach (var x in list) {
if (x < 0) list.Remove(x);
}
// GOOD:収集してから一括削除
var toRemove = list.Where(x => x < 0).ToList();
foreach (var x in toRemove) list.Remove(x);

null 許容と null 参照

Nullable を有効にすると、コンパイラが null 参照の検出を助けてくれます。null を直接返すと曖昧になるため、?? / ?. でフォールバックを提供してください。

1
2
3
4
5
6
7
// BAD:Nullable 無効 + 直接逆参照
string s = GetMaybe();
Console.WriteLine(s.Length); // NullReference の可能性
// GOOD:null 許容アノテーション + 空合体
string? maybe = GetMaybe();
Console.WriteLine((maybe ?? "").Length);

ローカル時刻 vs UTC

保存 / 転送する時刻は UTC(DateTimeKind.Utc)を使い、表示時にローカルへ変換します。Kind を混在させると時刻計算が静かに間違います。

1
2
3
4
5
6
7
// BAD:既定の Kind 未指定、変換の往復でズレる
var now = DateTime.Now;
// GOOD:UTC で明確に保存
var utc = DateTime.UtcNow;
var local = utc.ToLocalTime();
// DateTimeOffset はオフセット内蔵、タイムゾーン跨ぎにはこちら推奨

14.並行処理と非同期

Task ベースの非同期プログラミング、Parallel 並列処理、lock 同期、スレッド セーフ コレクション。

async / await

async メソッドは Task を返し、await はスレッドを解放して完了後に再開します。その間スレッドは他の作業に使えるため、ブロックしません。

1
2
3
4
5
6
async Task<string> FetchAsync() {
// await はスレッドを解放し、ブロックしない
var data = await httpClient.GetStringAsync(url);
return data;
}
string result = await FetchAsync(); // 呼び出し側も await

Task と戻り値

Task は非同期操作を表し、Task<T> は戻り値を持ちます。Task.Run は同期処理をスレッド プールへ置き、ContinueWith でチェーンします。

1
2
3
4
5
Task<int> t = Task.Run(() => Compute());
int result = await t;
var t2 = Task.Run(async () => await FetchAsync());
// Task.WhenAll:複数を並行待機
var all = await Task.WhenAll(t, t2);

Parallel 並列

Parallel.For / ForEach はスレッド プールを使い、独立した処理を並列実行します。CPU 集約型ではマルチコアを活用できますが、スレッド安全性と過剰並列化に注意してください。

1
2
3
4
5
Parallel.For(0, 100, i => {
Process(i); // 並行実行
});
Parallel.ForEach(items, item => Process(item));
// 最大並行度を制御:new ParallelOptions { MaxDegreeOfParallelism = 4 }

lock 同期

lock は同時に 1 つのスレッドのみがクリティカル セクションへ入ることを保証します。ロック オブジェクトは通常 private readonly フィールドとし、this でのロックは避けてください。

1
2
3
4
5
6
7
8
private readonly object _lock = new();
int _counter = 0;
void Increment() {
lock (_lock) {
_counter++; // 原子:マルチスレッド安全
}
}
// lock は Monitor.Enter/Exit の糖衣構文

Thread スレッド

Thread はスレッドを手動管理します: Start で開始、Join で待機。多くのシナリオでは Task / Parallel の方が適し、Thread は長時間のバックグラウンド タスクに使います。

1
2
3
4
5
6
var thread = new Thread(() => {
Console.WriteLine("worker");
});
thread.IsBackground = true; // バックグラウンドスレッド:メインスレッド終了で停止
thread.Start();
thread.Join(); // 完了を待つ

CancellationToken

CancellationToken は協調的キャンセルです: トークンが IsCancellationRequested をトリガし、async メソッドは OperationCanceledException を投げます。

1
2
3
4
5
6
7
var cts = new CancellationTokenSource();
cts.CancelAfter(TimeSpan.FromSeconds(5));
var data = await httpClient.GetStringAsync(url,
cts.Token);
try { await SlowAsync(cts.Token); }
catch (OperationCanceledException) { /* キャンセル済み */ }
// cts.Token.ThrowIfCancellationRequested() で自ら確認

並行コレクション

スレッド セーフなコレクション: ConcurrentDictionary、ConcurrentQueue、ConcurrentBag。通常コレクションを手動でロックして読み書きする代わりに使えます。

1
2
3
4
5
6
var dict = new ConcurrentDictionary<string, int>();
dict.TryAdd("a", 1);
dict.TryUpdate("a", 2, 1); // 条件付き更新
var queue = new ConcurrentQueue<int>();
queue.Enqueue(1);
if (queue.TryDequeue(out int v)) { }

非同期のベスト プラクティス

Async all the way: .Result でのブロックは避けてください。ライブラリ コードでは ConfigureAwait(false) を使い、sync コンテキストへ戻らないようにします。

1
2
3
4
5
6
7
// 全体を await チェーンに
async Task<string> OuterAsync() {
var data = await InnerAsync().ConfigureAwait(false);
return data;
}
// ライブラリ/非 UI 層では ConfigureAwait(false)
// UI イベントハンドラはコンテキストを保持(付けない)

15.ネットワークと HTTP

HttpClient によるリクエスト、JsonSerializer によるシリアライズ、WebSocket、Socket。

HttpClient 基本

HttpClient は HTTP リクエストを送ります。GetStringAsync は簡易テキスト取得、GetAsync は完全なレスポンス取得。HttpClient は長期間(シングルトンとして)再利用すべきです。

1
2
3
4
5
6
7
var http = new HttpClient();
string html = await http.GetStringAsync("https://example.com");
var resp = await http.GetAsync("https://example.com/api");
if (resp.IsSuccessStatusCode) {
string body = await resp.Content.ReadAsStringAsync();
}
// DI/IHttpClientFactory:インスタンスの長期再利用を推奨

POST と JSON

PostAsJsonAsync は JSON を送信し、PostAsync はカスタム コンテンツを送信します。レスポンスは ReadAsStringAsync で読みます。シリアライズには JsonSerializer を使います。

1
2
3
4
5
6
7
var data = new { Name = "Nick", Age = 30 };
var resp = await http.PostAsJsonAsync(url, data);
// JSON レスポンスを読み取る:
var person = await resp.Content
.ReadFromJsonAsync<Person>();
// 手動シリアライズ:
string json = JsonSerializer.Serialize(data);

ヘッダーとクエリ パラメータ

HttpRequestMessage で Headers(認証 / User-Agent)を設定し、URI でクエリ文字列を構築します。既定の User-Agent を拒否する API があることに注意してください。

1
2
3
4
5
6
var req = new HttpRequestMessage(HttpMethod.Get, url);
req.Headers.Add("Authorization", "Bearer token");
req.Headers.Add("User-Agent", "MyApp/1.0");
var uri = new UriBuilder(url);
uri.Query = "key=value&page=2"; // クエリ文字列を手動で組み立て
var resp = await http.SendAsync(req);

System.Text.Json

JsonSerializer.Serialize / Deserialize で JSON をシリアライズします。JsonSerializerOptions で大文字小文字 / 列挙型 / null 無視などの設定を行います。

1
2
3
4
5
6
7
var opts = new JsonSerializerOptions {
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
PropertyNameCaseInsensitive = true,
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
};
string json = JsonSerializer.Serialize(obj, opts);
var back = JsonSerializer.Deserialize<T>(json, opts);

Web API の呼び出し

組み合わせ: リクエストを構築 → ステータスを検証 → デシリアライズ。HttpStatusCode チェック、ReadFromJsonAsync なら 1 行で完結します。

1
2
3
4
5
6
7
using var resp = await http.GetAsync($"api/users/{id}");
if (resp.StatusCode == HttpStatusCode.NotFound) {
return null;
}
resp.EnsureSuccessStatusCode(); // 2xx 以外は例外をスロー
var user = await resp.Content.ReadFromJsonAsync<User>();
return user;

WebSocket

ClientWebSocket は全二重チャネルを確立し、SendAsync / ReceiveAsync で送受信します。リアルタイム配信(チャット、行情)に適しています。

1
2
3
4
5
6
7
8
using var ws = new ClientWebSocket();
await ws.ConnectAsync(uri, CancellationToken.None);
var buffer = new byte[1024];
var result = await ws.ReceiveAsync(buffer,
CancellationToken.None);
string msg = Encoding.UTF8.GetString(buffer, 0, result.Count);
await ws.SendAsync(payload, WebSocketMessageType.Text,
true, CancellationToken.None);

DNS と Socket

Dns.GetHostAddresses はホスト名を解決し、Socket は下位層トランスポートです。ほとんどのアプリでは HttpClient で十分で、Socket はカスタム プロトコル向けです。

1
2
3
4
5
var ips = await Dns.GetHostAddressesAsync("example.com");
// 低レベル Socket:
using var socket = new Socket(AddressFamily.InterNetwork,
SocketType.Stream, ProtocolType.Tcp);
await socket.ConnectAsync("example.com", 80);

ダウンロードとストリーム処理

大きなファイルのダウンロードは Stream でストリーミング書き込みし、メモリ消費を抑えます。HttpCompletionOption.ResponseHeadersRead はストリームを即座に返します。

1
2
3
4
5
using var resp = await http.GetAsync(url,
HttpCompletionOption.ResponseHeadersRead);
await using var stream = await resp.Content.ReadAsStreamAsync();
await using var file = File.Create("download.bin");
await stream.CopyToAsync(file); // ストリームで書き出し、大きなメモリを使わない

16.日付と時刻

DateTime と TimeSpan、タイムゾーンとオフセット、書式設定と解析。

DateTime 基本

DateTime は日付と時刻を表します: Now はローカル、UtcNow は UTC、Today は日付。AddDays / AddHours で日付演算を行います。

1
2
3
4
5
DateTime now = DateTime.Now;
DateTime utc = DateTime.UtcNow;
DateTime today = DateTime.Today; // 当日 00:00
DateTime nextWeek = now.AddDays(7);
var str = now.ToString("yyyy-MM-dd HH:mm");

TimeSpan 時間間隔

TimeSpan は間隔を表します: 2 つの時刻の差分から取得し、FromDays / FromHours で構築、Days / Hours で成分に分解します。

1
2
3
4
5
TimeSpan duration = end - start;
double hours = duration.TotalHours; // 総時間数(日数含む)
int h = duration.Hours; // 時間の部分のみ
var delay = TimeSpan.FromMinutes(5);
if (duration > TimeSpan.Zero) { }

DateTimeOffset

DateTimeOffset はタイム ゾーン オフセットを持ち、タイム ゾーンをまたぐシナリオで推奨されます。UTC と安全に変換でき、ローカル タイム ゾーンの曖昧さを避けられます。

1
2
3
4
5
DateTimeOffset now = DateTimeOffset.Now;
DateTimeOffset utc = DateTimeOffset.UtcNow;
var offset = new DateTimeOffset(2024, 1, 1, 12, 0, 0,
TimeSpan.FromHours(8)); // +08:00
DateTime utcTime = now.ToUniversalTime();

DateTimeKind

DateTime には Kind があり、Unspecified / Local / Utc のいずれかです。Kind 未指定のローカル時刻を UTC へ変換すると誤動作するため、Utc で保存してください。

1
2
3
4
5
DateTime local = DateTime.Now; // Kind = Local
DateTime utc = DateTime.UtcNow; // Kind = Utc
DateTime utcFromLocal = local.ToUniversalTime();
// 注意:new DateTime(...) は既定で Unspecified
// 保存/転送は UTC か DateTimeOffset に統一

解析と書式設定

DateTime.Parse / ParseExact で解析、TryParse は安全です。書式指定子: yyyy / MM / dd、HH / mm / ss、ffff はミリ秒。

1
2
3
4
5
var d = DateTime.Parse("2024-01-01");
bool ok = DateTime.TryParse("2024-01-01", out var dt);
var exact = DateTime.ParseExact("01/02/2024",
"MM/dd/yyyy", CultureInfo.InvariantCulture);
string s = dt.ToString("O"); // ISO 8601 往復安全

タイムゾーン変換

TimeZoneInfo でタイム ゾーン変換を行います: FindSystemTimeZoneById、ConvertTimeFromUtc。サーバーは通常 UTC で保存し、表示時にローカルへ変換します。

1
2
3
4
5
DateTime utc = DateTime.UtcNow;
TimeZoneInfo shanghai =
TimeZoneInfo.FindSystemTimeZoneById("Asia/Shanghai");
DateTime local = TimeZoneInfo.ConvertTimeFromUtc(utc, shanghai);
// 固定オフセットなら DateTimeOffset の方が簡単

Stopwatch 計測

Stopwatch は高精度(ミリ秒単位)の時間計測を行います。Start / Stop / Elapsed / ElapsedMilliseconds はパフォーマンス測定の定番です。

1
2
3
4
5
6
var sw = Stopwatch.StartNew();
DoWork();
sw.Stop();
Console.WriteLine($"Elapsed: {sw.ElapsedMilliseconds} ms");
// または sw.Elapsed.TotalMilliseconds
// Restart() でタイマーを再利用

DateOnly と TimeOnly

DateOnly は日付のみ、TimeOnly は時刻のみを表す型(C# 10)で、タイム ゾーンの負担がありません。誕生日、カレンダー、シフト スケジュールなどに向きます。

1
2
3
4
5
6
DateOnly today = DateOnly.FromDateTime(DateTime.Now);
DateOnly birthday = new(2000, 6, 15);
TimeOnly start = new(9, 30);
TimeOnly end = new(18, 0);
TimeSpan span = end - start; // 8.5 時間
Console.WriteLine(today); // 今日の日付を出力

17.プロセスとシステム

外部プロセスの起動、環境変数、パス、システム情報。

プロセスの起動

Process.Start は外部プログラム(シェル コマンド、他の実行ファイル)を起動します。ArgumentList で引数を渡すと文字列連結によるインジェクションを防げます。

1
2
3
4
5
6
var psi = new ProcessStartInfo("git") {
ArgumentList = { "log", "-1" }, // 引数配列、インジェクションなし
RedirectStandardOutput = true, // 出力をキャプチャ
};
using var proc = Process.Start(psi)!;
string output = await proc.StandardOutput.ReadToEndAsync();

環境変数

Environment.GetEnvironmentVariable で読み取り、SetEnvironmentVariable で書き込み(プロセス レベル)。GetEnvironmentVariables で全部取得できます。

1
2
3
4
5
string? path = Environment.GetEnvironmentVariable("PATH");
Environment.SetEnvironmentVariable("MY_VAR", "value");
// システムレベル:
Environment.SetEnvironmentVariable("MY_VAR", "v",
EnvironmentVariableTarget.Machine);

システム情報

Environment はシステム情報を提供します: OSVersion、MachineName、CurrentDirectory、ProcessorCount、TickCount。

1
2
3
4
5
string os = Environment.OSVersion.ToString();
string machine = Environment.MachineName;
string dir = Environment.CurrentDirectory;
int cores = Environment.ProcessorCount;
string user = Environment.UserName;

コマンドライン解析

args にはコマンドライン引数が渡され、Environment.GetCommandLineArgs はプログラム名を含む完全なセットを取得します。CLI ツールでの引数解析に使います。

1
2
3
4
5
6
7
// Program.cs トップレベル
if (args.Length == 0) {
Console.Error.WriteLine("usage: app <file>");
return 1;
}
// 先頭 2 文字が -- ならオプション扱い(自前で実装)
foreach (var a in args.Skip(1)) { /* 処理 */ }

終了とシグナル

Environment.Exit(1) は即時終了、ExitCode は終了コードを設定します。AppDomain.ProcessExit イベントでクリーンアップを行います。

1
2
3
4
5
AppDomain.CurrentDomain.ProcessExit += (s, e) => {
SaveState(); // 終了前のクリーンアップ
};
Environment.ExitCode = 2; // ランタイムに自然終了させる
// または直接 Environment.Exit(0)

プロセスの列挙

Process.GetProcesses はシステム プロセスを列挙し、Id / ProcessName / WorkingSet64 などの情報を読み取れます。Kill でプロセスを終了します。

1
2
3
4
5
6
foreach (var p in Process.GetProcesses()) {
Console.WriteLine($"{p.Id}: {p.ProcessName}");
}
var current = Process.GetCurrentProcess();
long mem = current.WorkingSet64;
Process.GetProcessById(pid)?.Kill(); // 指定プロセスを終了

Windows レジストリ

Microsoft.Win32.Registry はレジストリ(Windows 限定)の読み書きを行います。GetValue / SetValue でキーにアクセスし、適切な権限が必要です。

1
2
3
4
5
6
7
8
using Microsoft.Win32;
// 読み取り:
object? val = Registry.CurrentUser.OpenSubKey("Software\\App")
?.GetValue("Setting");
// 書き込み:
using var key = Registry.CurrentUser.CreateSubKey("Software\\App");
key.SetValue("Setting", 42);
// 注意:Windows のみ、クロスプラットフォームでは依存を避ける

アプリケーション パス

AppContext.BaseDirectory はプログラムの実行ディレクトリ、Environment.ProcessPath は現在のプロセス パスです。リソース ファイルの配置に使います。

1
2
3
4
string baseDir = AppContext.BaseDirectory; // プログラムの実行ディレクトリ
string? path = Environment.ProcessPath; // 現在プロセスの完全パス
string file = Path.Combine(baseDir, "data.json");
// 相対パスは作業ディレクトリ依存で呼び出し元の影響を受けやすい、BaseDirectory を使う

18.正規表現

Regex によるマッチ、キャプチャ グループ、置換、一般的なパターン。

Regex 基本

Regex.IsMatch で判定、Matches ですべて取得、Match で最初の 1 件を取得します。@ の逐語的文字列を使うと二重エスケープを防げます。

1
2
3
4
5
6
var r = new Regex(@"^\d{3}-\d{4}$");
bool ok = r.IsMatch("123-4567");
foreach (Match m in Regex.Matches(text, @"\b\w+\b")) {
Console.WriteLine(m.Value);
}
// @ 逐語:\d は正規表現の \d、文字列エスケープではない

キャプチャ グループ

丸括弧でサブ文字列をキャプチャし、Groups[1] でインデックス参照、名前付きグループ (?<name>...) は Groups["name"] で参照します。フィールド抽出に使います。

1
2
3
4
5
6
7
var r = new Regex(@"(\d{4})-(\d{2})-(\d{2})");
var m = r.Match("date: 2024-01-01");
if (m.Success) {
string year = m.Groups[1].Value; // 2024
}
// 名前付きグループ:Regex(@"(?<year>\d{4})-")
string y = m.Groups["year"].Value;

置換

Regex.Replace でパターン置換し、$1 / $2 でキャプチャ グループを参照します。フォーマット、マスキング、テキスト クリーンアップに利用できます。

1
2
3
4
5
string masked = Regex.Replace(
phone, @"(\d{3})\d{4}(\d{4})", "$1****$2");
string cleaned = Regex.Replace(
text, @"[\s\t]+", " ");
// 置換コールバック:Regex.Replace(text, pat, m => Process(m.Value))

RegexOptions

RegexOptions.IgnoreCase で大文字小文字を無視、Multiline で ^ / $ を行頭 / 行末にマッチ、Compiled でコンパイルして再利用を高速化。

1
2
3
4
5
var r = new Regex(@"^[a-z]+$",
RegexOptions.IgnoreCase | RegexOptions.Multiline);
// Compiled:正規表現を何度も使う場合にのみ有効
var fast = new Regex(@"pattern", RegexOptions.Compiled);
// IgnorePatternWhitespace はパターン内コメントを許可

一般的なパターン

メール、URL、IP、電話番号の一般的な正規表現です。注意: 複雑な検証(本当のメール判定など)には正規表現ではなく専用ライブラリを使ってください。

1
2
3
4
var email = @"^[^@\s]+@[^@\s]+\.[^@\s]+$";
var ipv4 = @"^(?:\d{1,3}\.){3}\d{1,3}$";
var url = @"^https?://[^\s]+$";
// 簡易検証なら十分;本番向けは専用ライブラリを使う

量指定子とアンカー

* は 0 回以上、+ は 1 回以上、? は 0 回または 1 回、{n,m} は指定回数。^ は行頭、$ は行末、\b は単語境界。

1
2
3
4
5
6
@"\d+" // 1 個以上の数字
@"colou?r" // color または colour
@"\d{2,4}" // 2 〜 4 桁の数字
@"^start" // start で始まる(行頭)
@"end$" // end で終わる
@"\bword\b" // 単独単語 word

アサーションと先読み・後読み

ゼロ幅アサーションは文字を消費しません: (?<=...) は後読み、(?=...) は先読み、(?!...) は否定先読み。位置条件のマッチングに使います。

1
2
3
4
5
6
7
8
// 後読み (?<=@):@ の後ろのメールドメイン部分を抽出
string domain = Regex.Match("[email protected]",
@"(?<=@)[^@]+").Value; // example.com
// 先読み (?=\d):文字の直後に数字があるか判定
bool hasDigit = Regex.IsMatch("a1", @"[a-z](?=\d)"); // true
// 否定先読み (?!...):world で始まらない単語に一致
var first = Regex.Match("hello world",
@"\b(?!world)\w+").Value; // hello

バックトラッキングと ReDoS

入れ子になった量指定子や選択肢分岐はバックトラッキングを引き起こし、悪意ある入力で指数時間マッチ(ReDoS)になることがあります。タイムアウト制限やバックトラッキング無効化が必要です。

1
2
3
4
5
6
7
8
// 危険:ネスト量詞は長い入力で指数関数的なバックトラックが起こり得る
// var risky = new Regex(@"(a+)+$");
// 対策その 1:常にマッチタイムアウトを設定
var withTimeout = new Regex(@"(a+)+$", RegexOptions.None,
TimeSpan.FromSeconds(2));
// 対策その 2:NonBacktracking でバックトラック無効(.NET 7+)
var safe = new Regex(@"(a+)+$", RegexOptions.NonBacktracking);
// ユーザー入力の正規表現やテキストでは必ずタイムアウトと長さ制限を

19.ビルドとデバッグ

dotnet CLI、プロジェクト構成、デバッグ、ログ、条件付きコンパイル。

dotnet CLI

dotnet build はビルド、run は実行、test はテスト、publish は発行。--configuration Release でリリース構成を発行します。

1
2
3
4
5
// $ dotnet build # Debug をコンパイル
// $ dotnet run # コンパイルして実行
// $ dotnet test # テストを実行
// $ dotnet publish -c Release -o out
// $ dotnet clean # 成果物をクリア

csproj 構成

csproj はビルドを制御します: TargetFramework、Nullable、ImplicitUsings、PackageReference 依存関係、LangVersion。

1
2
3
4
5
6
7
8
9
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<LangVersion>12</LangVersion>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
</ItemGroup>

デバッグとログ

Debug.WriteLine は Debug ビルドでのみ、Trace はすべてのビルドで動作します。ILogger はログ レベルを記録し、Console.WriteLine は一時的なデバッグに使います。

1
2
3
4
5
Debug.WriteLine($"value={x}"); // Debug ビルドのみ
Trace.TraceInformation("start"); // すべてのビルドで実行
var logger = LoggerFactory.Create(b =>
b.AddConsole()).CreateLogger("App");
logger.LogInformation("Processing {Id}", id);

条件付きコンパイル

#if DEBUG / #elif / #endif でシンボルによりコードを切り替えます。プロジェクト レベルの DefineConstants でカスタム シンボルを定義します。

1
2
3
4
5
6
#if DEBUG
Console.WriteLine("debug build");
#elif RELEASE
Console.WriteLine("release build");
#endif
// csproj:<DefineConstants>TRACE;MY_FLAG</DefineConstants>

単体テスト

xUnit / NUnit / MSTest で動作をアサートします。[Fact] がテスト メソッド、Assert.Equal がアサーション。dotnet test で実行します。

1
2
3
4
5
6
7
[Fact]
public void Add_ReturnsSum() {
var calc = new Calculator();
var result = calc.Add(2, 3);
Assert.Equal(5, result);
}
// xUnit テストクラス:public class CalculatorTests

NuGet パッケージ管理

dotnet add package で依存関係を追加し、restore で復元します。PackageReference が csproj にバージョンを記録します。

1
2
3
4
// $ dotnet add package Newtonsoft.Json
// $ dotnet restore # 依存を復元
// $ dotnet list package # 依存を確認
// 依存は csproj の PackageReference に記録

発行と単一ファイル

dotnet publish は本番バージョンを発行し、PublishSingleFile で単一ファイル化、SelfContained でランタイム不要にします。

1
2
3
4
5
6
7
// Release を out ディレクトリに発行
// $ dotnet publish -c Release -o out
// 単一ファイルにまとめる(対象機に .NET のインストールが必要):
// $ dotnet publish -r win-x64 -p:PublishSingleFile=true -o out
// 自己完結型(ランタイム込み、対象機のインストール不要、サイズ大):
// $ dotnet publish -r win-x64 --self-contained true -o out
// 未使用コードをトリミングしてサイズ削減:-p:PublishTrimmed=true

静的アナライザー

Roslyn アナライザーはコンパイル時にコード品質と潜在的な欠陥をチェックし、警告をエラーに昇格させて問題のビルドをブロックできます。

1
2
3
4
5
6
// アナライザはコンパイル時にコード品質と潜在的な欠陥を報告
class AnalyzerDemo {
public int Add(int a, int b) => a + b;
}
// 警告をエラー扱い:dotnet build -warnaserror
// 組み込み品質アナライザを有効化:csproj で AnalysisLevel を latest に

公式リンク

公式ドキュメントとリソースへの直接リンク。

このチートシートについて

このページは C# 12(.NET 8)の自己完結型クイック リファレンスで、言語の中核、BCL のよく使う型、そして実際のプロジェクトで日常の約 80% を占める非同期プログラミングを網羅しています。内容はモダンなイディオムに重点を置いています: プロパティと自動プロパティ、LINQ、async/await、record 型、パターン マッチング、null 許容参照型。C# は Anders Hejlsberg によって 2000 年に .NET プラットフォームと共に発表され、Windows エコシステム、Unity ゲーム開発、バックエンド サービスの中心言語として、型安全性と生産性の調和を重視しています。 19 のセクションはそれぞれ一つのトピックに焦点を当てます: 基本構文、変数、型と参照 / 値セマンティクス、制御フロー、関数、文字列、コレクション、メモリ管理(GC)、オブジェクト指向、エラー処理、I/O、よくある落とし穴、並行処理、ネットワーク、時間、プロセス、正規表現、ビルド ツール。各サブセクションには「コンセプト紹介 + そのままコピーできるコード スニペット」が付属しています。 すべてのコードとテキストはブラウザでローカルにレンダリングされ、データは一切デバイスを出ません。正確なリファレンスは Microsoft Learn の公式ドキュメントを参照してください。

バージョン 2.1.0