C# チートシート
C# 14・.NET 10をベースにしたチートシートです。日常的に使う構文と標準ライブラリを、説明とサンプルでまとめています。
C#バージョンごとの比較
Section titled “C#バージョンごとの比較”このページの基準となる安定版C# 14までの主要な追加機能をまとめます。機能の登場時期を示す表で、各実行環境のサポート期間を示すものではありません。プレビュー機能は含めていません。
| C# | 公開年 | 主な追加機能 |
|---|---|---|
| 1.0 / 1.2 | 2002 / 2003 | クラス、構造体、インターフェース、デリゲート/1.2で細部の改善 |
| 2.0 | 2005 | ジェネリクス、Nullable値型、イテレーター、匿名メソッド |
| 3.0 | 2007 | LINQ、ラムダ、拡張メソッド、var、匿名型 |
| 4.0 | 2010 | dynamic、名前付き・省略可能引数、ジェネリクスの共変性・反変性 |
| 5.0 | 2012 | async / await、呼び出し元情報 |
| 6.0 | 2015 | 文字列補間、?.、nameof、式形式のメンバー |
| 7.0 | 2017 | タプル・分解、パターンマッチング、ローカル関数、out var |
| 7.1 | 2017 | async Main、default リテラル |
| 7.2 | 2017 | readonly struct、ref struct、in 引数 |
| 7.3 | 2018 | unmanaged / Enum / Delegate 制約 |
| 8.0 | 2019 | Nullable参照型、switch式、非同期ストリーム、範囲、using宣言 |
| 9.0 | 2020 | record、init、トップレベルステートメント、ターゲット型の new |
| 10 | 2021 | record struct、global using、ファイルスコープ名前空間 |
| 11 | 2022 | raw文字列、required、リストパターン、ジェネリック数学 |
| 12 | 2023 | コレクション式、クラス・構造体のプライマリコンストラクター |
| 13 | 2024 | params コレクション、System.Threading.Lock 対応、ref structの制約緩和 |
| 14 | 2025 | 拡張メンバー、field、null条件付き代入 |
ターゲットフレームワークと既定の言語バージョン
Section titled “ターゲットフレームワークと既定の言語バージョン”現在のSDKで LangVersion を上書きしない場合、ターゲットフレームワークから言語バージョンが選ばれます。これは古いC#が最初に公開されたときの環境とは別の対応表です。
| ターゲット | 既定のC# |
|---|---|
| .NET 10 | 14 |
| .NET 9 | 13 |
| .NET 8 | 12 |
| .NET 7 | 11 |
| .NET 6 | 10 |
| .NET 5 | 9.0 |
| .NET Core 3.x / .NET Standard 2.1 | 8.0 |
| .NET Core 2.x / .NET Standard 2.0 / .NET Framework | 7.3 |
通常はターゲットフレームワークの既定値を使います。LangVersion を latest にするとインストール済みコンパイラーに結果が依存し、不足するランタイムAPIが追加されるわけではありません。global.json はSDK、TargetFramework は対象環境、LangVersion は受け付ける構文を指定します。対象環境の対応表を超える言語バージョンの使用はサポートされません。
Visual Studioとのバージョン対応
Section titled “Visual Studioとのバージョン対応”各安定版C#が導入されたWindows版Visual Studioのバージョンをまとめます。導入時の対応表であり、現在サポート中のIDE一覧ではありません。Visual Studio Codeは別製品です。
| C# | 導入時のVisual Studio |
|---|---|
| 1.0 | Visual Studio .NET 2002 |
| 1.2 | Visual Studio .NET 2003 |
| 2.0 | Visual Studio 2005 |
| 3.0 | Visual Studio 2008 |
| 4.0 | Visual Studio 2010 |
| 5.0 | Visual Studio 2012 |
| 6.0 | Visual Studio 2015 |
| 7.0 | Visual Studio 2017 (15.0) |
| 7.1 | Visual Studio 2017 (15.3) |
| 7.2 | Visual Studio 2017 (15.5) |
| 7.3 | Visual Studio 2017 (15.7) |
| 8.0 | Visual Studio 2019 (16.3) |
| 9.0 | Visual Studio 2019 (16.8) |
| 10 | Visual Studio 2022 (17.0) |
| 11 | Visual Studio 2022 (17.4) |
| 12 | Visual Studio 2022 (17.8) |
| 13 | Visual Studio 2022 (17.12) |
| 14 | Visual Studio 2026 (18.0) |
新しいコンパイラーでも古いC#構文は扱えますが、古いプロジェクトを開いてビルドできるかは、プロジェクト形式・ターゲットフレームワーク・インストール済みワークロードにも依存します。製品名の年とバージョン番号は別で、Visual Studio 2022は17.x系、Visual Studio 2026は18.x系です。
近年の.NETをVisual Studioでターゲットにする条件
Section titled “近年の.NETをVisual Studioでターゲットにする条件”| ターゲット | 既定のC# | ターゲット指定に必要なVisual Studio |
|---|---|---|
| .NET 8 | 12 | Visual Studio 2022 (17.8) 以降 |
| .NET 9 | 13 | Visual Studio 2022 (17.12) 以降 |
| .NET 10 | 14 | Visual Studio 2026 (18.0) 以降 |
古いVisual StudioからSDKを使用できることと、そのSDKの最新ランタイムをターゲットにできることは別です。例えば.NET 10 SDK 10.0.100はVisual Studio 2022 17.14でも使用できますが、Visual Studio内で net10.0 をターゲットにするには2026 18.0以降が必要です。global.json で選択するSDKのフィーチャーバンドと、IDEが対応するSDKも確認します。Visual Studio外の dotnet build では、インストール済みSDKとプロジェクト設定がビルド環境を決めます。
公式 — SDK・MSBuild・Visual Studioの互換性
よく使う書き方の変化
Section titled “よく使う書き方の変化”| 用途 | 従来の書き方 | 新しい書き方 | 導入版 |
|---|---|---|---|
| 文字列補間 | "Hello, " + name |
$"Hello, {name}" |
6 |
| null時の代替値を代入 | if (name == null) name = "guest"; |
name ??= "guest"; |
8 |
| オブジェクト生成 | List<int> values = new List<int>(); |
List<int> values = new(); |
9 |
| 配列の初期化 | int[] values = new[] { 1, 2 }; |
int[] values = [1, 2]; |
12 |
| nullでなければ代入 | if (person is not null) person.Name = "Ada"; |
person?.Name = "Ada"; |
14 |
従来の書き方も引き続き使えます。新構文は重複を減らせますが、一般に高速化を意味するわけではありません。本文のraw文字列・required はC# 11、コレクション式はC# 12、System.Threading.Lock の例はC# 13と.NET 9以降を使います。
インストール・プロジェクト作成
Section titled “インストール・プロジェクト作成”.NET公式ダウンロードから、OSに合った.NET 10 SDKをインストールします。Runtimeだけではビルドできません。既存プロジェクトでは global.json のSDK指定と .csproj のターゲットフレームワークを優先します。
dotnet --versiondotnet --list-sdksdotnet new console -n HelloCSharp --framework net10.0cd HelloCSharpdotnet rundotnet build -c Release以下の例は、コンソールプロジェクトの Program.cs に記述します。トップレベルステートメントを使うと、Main メソッドの宣言を省略できます。
// 標準出力へ表示Console.WriteLine("Hello, C#!");Console.WriteLine($"Arguments: {args.Length}");このページのC#コードブロックは、特記しない限り、それぞれ独立して Program.cs に置き換えて試せます。net10.0、ImplicitUsings と Nullable が enable のコンソールプロジェクトを想定しています。型宣言はトップレベルステートメントの後に置きます。
// は行コメント、/* ... */ はブロックコメント、/// はXMLドキュメントコメントです。
Console.WriteLine(MathHelpers.Add(2, 3));
public static class MathHelpers{ /// <summary>2つの整数を加算します。</summary> public static int Add(int left, int right) => left + right;}変数・定数・基本型
Section titled “変数・定数・基本型”var は初期値から型を推論します。動的型ではなく、宣言時に型が決まります。ローカル変数は使用前に代入が必要です。
int count = 3;long population = 8_000_000_000L;double ratio = 0.5;decimal price = 1_980.50m;bool enabled = true;char initial = 'C';string language = "C#";var names = new List<string>();const int MaxRetries = 3;Console.WriteLine($"{count}, {population}, {ratio}, {price}, {enabled}, {initial}, {language}, {names.Count}, {MaxRetries}");| 型 | 用途・注意点 |
|---|---|
byte / sbyte |
8ビット符号なし/符号付き整数 |
short / ushort |
16ビット整数 |
int / uint |
32ビット整数 |
long / ulong |
64ビット整数 |
nint / nuint |
プラットフォーム依存サイズの整数 |
float / double |
2進浮動小数点。float リテラルには f |
decimal |
10進数の計算。リテラルには m。丸めや範囲の制約はある |
bool |
true / false |
char |
UTF-16コード単位1つ。常に1文字とは限らない |
string |
不変のUTF-16文字列 |
object |
全ての型の共通の基底型。値型の代入はボックス化を伴う |
フィールドや配列要素は既定値(数値は 0、bool は false、参照型は null)で初期化されます。readonly フィールドは宣言時またはコンストラクターで代入でき、コンパイル時定数の const とは異なります。
型変換・数値演算
Section titled “型変換・数値演算”整数同士の除算は整数になります。外部入力の変換には、失敗を戻り値で扱える TryParse が便利です。
Console.WriteLine(5 / 2); // 2Console.WriteLine(5 / 2.0); // 2.5if (int.TryParse("42", out int value)) Console.WriteLine(value);
double source = 3.9;int truncated = (int)source;Console.WriteLine(truncated);try{ int max = int.MaxValue; Console.WriteLine(checked(max + 1));}catch (OverflowException){ Console.WriteLine("Overflow");}checked は整数演算・変換のオーバーフロー検出に使います。書式付きの数値や日時を保存・交換する場合は、カルチャーも明示します。
null・Nullable参照型
Section titled “null・Nullable参照型”int? はnullを持てる値型です。string? はnullを許容する参照型としてコンパイラーに意図を伝えます。Nullable参照型の警告は実行時チェックを追加しません。
string? name = null;int? length = name?.Length;Console.WriteLine(length ?? 0);name ??= "guest";if (name is { Length: > 0 }) Console.WriteLine(name.ToUpperInvariant());
int? score = null;Console.WriteLine(score.GetValueOrDefault());?. はnullの場合にアクセスを省略し、?? は代替値、??= はnullの場合の代入です。! は警告を抑制するだけで、nullを安全にしません。
文字列は不変です。補間は $"..."、パスなどでバックスラッシュをそのまま書くには @"..."、複数行のテキストにはraw文字列リテラルを使えます。
string name = "Ada";string path = @"C:\work\notes.txt";string json = """ {"name":"Ada","active":true} """;Console.WriteLine($"Hello, {name}! {path}");Console.WriteLine(json);Console.WriteLine(string.Join(" | ", " alpha,beta ".Trim().Split(',')));Console.WriteLine(string.Join(" / ", new[] { "alpha", "beta" }));Console.WriteLine("CSharp".Contains("sharp", StringComparison.OrdinalIgnoreCase));Console.WriteLine(name[..2]);識別子などの比較では StringComparison.Ordinal / OrdinalIgnoreCase を明示します。表示用の自然言語ではカルチャーを考慮します。大量の連結には StringBuilder が使えます。
制御構文・パターンマッチング
Section titled “制御構文・パターンマッチング”if、for、foreach、while、do を使えます。switch 式はパターンから値を選びます。
int score = 85;if (score >= 60) Console.WriteLine("Pass");
string grade = score switch{ >= 90 => "A", >= 80 => "B", >= 60 => "C", _ => "D"};Console.WriteLine(grade);for (int i = 0; i < 3; i++) Console.WriteLine(i);foreach (int number in new[] { 1, 2, 3 }){ if (number == 2) continue; Console.WriteLine(number);}int remaining = 2;while (remaining > 0) remaining--;do { remaining++; } while (remaining < 1);
object item = "hello";if (item is string { Length: > 0 } text) Console.WriteLine(text);int[] values = [1, 2, 3];Console.WriteLine(values is [1, .., 3]);break はループやswitch文を抜け、continue は次の反復に進みます。switch文の各節は通常 break や return で終え、暗黙に次の節へ進みません。
メソッド・引数・タプル
Section titled “メソッド・引数・タプル”ローカル関数も宣言できます。複数の結果にはタプル、可変長引数には params、名前付き引数には 名前: 値 を使います。
Console.WriteLine(Greet(name: "Ada"));Console.WriteLine(Sum(1, 2, 3));var (min, max) = Bounds([8, 2, 5]);Console.WriteLine($"{min}..{max}");int count = 1;Increment(ref count);Console.WriteLine(count);
static string Greet(string name, string prefix = "Hello") => $"{prefix}, {name}";static int Sum(params int[] values) => values.Sum();static (int Min, int Max) Bounds(int[] values) => (values.Min(), values.Max());static void Increment(ref int value) => value++;ref は初期化済み変数を参照渡し、out は呼び出し先で必ず代入する出力、in は読み取り専用の参照渡しです。通常の引数は値渡しで、参照型の場合は参照の値がコピーされます。
配列・List・Dictionary・HashSet
Section titled “配列・List・Dictionary・HashSet”配列は長さが固定、List<T> は要素数を変更できる配列ベースのコレクションです。Dictionary<TKey, TValue> はキーと値、HashSet<T> は重複しない値を保持します。
int[] numbers = [10, 20, 30];Console.WriteLine(numbers[^1]);int[] firstTwo = numbers[..2];List<string> names = ["Ada", "Linus"];names.Add("Grace");names.Remove("Linus");Console.WriteLine($"{numbers.Length}, {firstTwo.Length}, {names.Count}");
var scores = new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase){ ["Ada"] = 90};if (scores.TryGetValue("ada", out int score)) Console.WriteLine(score);scores["Grace"] = 95;foreach (var (name, points) in scores) Console.WriteLine($"{name}: {points}");
HashSet<int> unique = [1, 1, 2];Console.WriteLine(unique.Add(2));Console.WriteLine(unique.Contains(1));配列の範囲アクセス numbers[..2] は新しい配列を作ります。List<T> の Capacity は確保済み容量、Count は実際の要素数です。通常のコレクションを複数スレッドから同時に変更する場合は同期が必要です。
クラス・プロパティ・アクセス修飾子
Section titled “クラス・プロパティ・アクセス修飾子”クラスは参照型です。required は初期化時の指定を要求し、init は初期化時だけ設定できるプロパティを作ります。
var account = new Account("A-001") { Owner = "Ada" };account.Deposit(100m);Console.WriteLine($"{account.Id}: {account.Owner}, {account.Balance}");
public sealed class Account(string id){ public string Id { get; } = id; public required string Owner { get; init; } public decimal Balance { get; private set; }
public void Deposit(decimal amount) { if (amount <= 0) throw new ArgumentOutOfRangeException(nameof(amount)); Balance += amount; }}| 修飾子 | アクセス範囲 |
|---|---|
public |
制限なし(包含する型の範囲内) |
private |
宣言した型の内部 |
protected |
宣言した型と派生型 |
internal |
同一アセンブリ |
protected internal |
同一アセンブリ、または派生型 |
private protected |
同一アセンブリ内の派生型と宣言した型 |
sealed は継承を禁止します。プライマリコンストラクターの引数は、通常のクラスでは自動的に公開プロパティになりません。required は値の妥当性を保証しません。
構造体・record・等値性
Section titled “構造体・record・等値性”struct は値型で、代入すると値がコピーされます。record は値に基づく等値比較を生成します。既定の record は参照型、record struct は値型です。
var original = new Person("Ada", 36);var updated = original with { Age = 37 };Console.WriteLine(original == new Person("Ada", 36));Console.WriteLine(updated);var point = new Point(1, 2);var copy = point with { X = 5 };Console.WriteLine($"{point} / {copy}");
public record Person(string Name, int Age);public readonly record struct Point(int X, int Y);with は浅いコピーです。参照型のメンバーは共有されるため、recordでも深い不変性は保証されません。値型にも参照型のフィールドを含められ、その参照はコピー先と共有されます。
継承・インターフェース
Section titled “継承・インターフェース”クラスが継承できる基底クラスは1つですが、インターフェースは複数実装できます。共通の契約をインターフェースで表現します。
IShape shape = new Circle(3);Console.WriteLine(shape.Area);Animal animal = new Dog();animal.Speak();
public interface IShape{ double Area { get; }}public sealed class Circle(double radius) : IShape{ public double Area => Math.PI * radius * radius;}public abstract class Animal{ public abstract void Speak();}public sealed class Dog : Animal{ public override void Speak() => Console.WriteLine("Woof");}基底クラスで実装付きの上書き可能なメソッドを定義するには virtual、派生クラスで上書きするには override を使います。new によるメンバーの隠蔽とは異なります。
enum・フラグ
Section titled “enum・フラグ”列挙型は名前付きの整数値を表します。フラグでは各ビットに値を割り当てます。
Access access = Access.Read | Access.Write;Console.WriteLine(access.HasFlag(Access.Read));Console.WriteLine((access & Access.Write) != 0);Console.WriteLine(Enum.IsDefined(Status.Ready));
public enum Status { Unknown, Ready, Done }[Flags]public enum Access { None = 0, Read = 1, Write = 2, Execute = 4 }整数からキャストすれば未定義の値も作れます。外部入力では妥当性を確認します。フラグの組み合わせは Enum.IsDefined では個別定義がないと認められません。
ジェネリクス
Section titled “ジェネリクス”型パラメーターを使うと、型安全性を保ったまま処理やデータ構造を共通化できます。where は使用可能な型を制約します。
Console.WriteLine(Max(3, 7));var box = new Box<string>("hello");Console.WriteLine(box.Value);
static T Max<T>(T left, T right) where T : IComparable<T> => left.CompareTo(right) >= 0 ? left : right;
public sealed class Box<T>(T value){ public T Value { get; } = value;}代表的な制約は class、struct、notnull、インターフェース、new() です。new() はpublicの引数なしコンストラクターを要求し、他の通常の制約より後に書きます。
ラムダ・デリゲート・イベント
Section titled “ラムダ・デリゲート・イベント”Func<...> は戻り値あり、Action<...> は戻り値なしの処理を保持します。ラムダは外側の変数をキャプチャできます。イベントは発行元から購読者へ通知します。
int factor = 2;Func<int, int> multiply = value => value * factor;factor = 3;Console.WriteLine(multiply(4));Action<string> print = message => Console.WriteLine(message);print("Ready");
var counter = new Counter();EventHandler handler = (_, _) => Console.WriteLine("Changed");counter.Changed += handler;counter.Increment();counter.Changed -= handler;
public sealed class Counter{ public int Value { get; private set; } public event EventHandler? Changed; public void Increment() { Value++; Changed?.Invoke(this, EventArgs.Empty); }}キャプチャされるのは変数であり、その時点の値のスナップショットではありません。長寿命の発行元へのイベント購読は、不要になったら解除します。キャプチャ不要のラムダには static を付けられます。
LINQはコレクションなどに対して、抽出・変換・集約を記述する仕組みです。メソッド構文とクエリ構文があります。
int[] numbers = [1, 2, 3, 4, 5];var query = numbers.Where(n => n % 2 == 0).Select(n => n * 10);int[] result = query.ToArray();Console.WriteLine(string.Join(", ", result));Console.WriteLine(numbers.Any(n => n > 4));Console.WriteLine(numbers.All(n => n > 0));Console.WriteLine(numbers.Sum());Console.WriteLine(numbers.FirstOrDefault(n => n > 10, -1));
var descending = from n in numbers where n >= 3 orderby n descending select n;Console.WriteLine(string.Join(", ", descending));foreach (var group in numbers.GroupBy(n => n % 2)) Console.WriteLine($"{group.Key}: {group.Count()}");| 操作 | 主なメソッド |
|---|---|
| 抽出・射影 | Where, Select, SelectMany |
| 並べ替え | OrderBy, OrderByDescending, ThenBy |
| 先頭・末尾・単一 | First, Last, Single と各 OrDefault |
| 件数制限 | Take, Skip, TakeWhile |
| 集約 | Count, Sum, Average, Min, Max, Aggregate |
| 集合 | Distinct, Union, Intersect, Except |
| 実体化 | ToArray, ToList, ToDictionary |
Where や Select は通常、列挙時に実行されます(遅延実行)。複数回列挙すると処理も再実行されるため、結果を固定したい場合は実体化します。SingleOrDefault も複数件なら例外になります。ToDictionary はキー重複で例外になります。IQueryable<T> はプロバイダーによってSQLなどに変換され、通常のメモリ上のLINQと実行条件が異なります。
イテレーター・yield
Section titled “イテレーター・yield”yield return で要素を順に返すイテレーターを定義できます。全件を先に配列へ格納する必要はありません。
foreach (int value in CountUp(3)) Console.WriteLine(value);
static IEnumerable<int> CountUp(int count){ for (int i = 0; i < count; i++) yield return i;}イテレーター本体の処理や例外は通常、メソッドの呼び出し時ではなく列挙時に発生します。yield break で列挙を終了できます。
回復できる例外を具体的な型で捕捉します。リソース解放には finally や using を使います。
try{ Console.WriteLine(ParsePositive("-1"));}catch (ArgumentOutOfRangeException ex){ Console.WriteLine(ex.ParamName);}catch (FormatException){ Console.WriteLine("Invalid number");}finally{ Console.WriteLine("Finished");}
static int ParsePositive(string text){ int value = int.Parse(text); if (value <= 0) throw new ArgumentOutOfRangeException(nameof(text), "Must be positive."); return value;}再スローは throw; を使い、元のスタックトレースを保ちます。throw ex; はスタックトレースをリセットします。想定内の入力エラーでは、例外を通常の分岐として使うより TryParse などを検討します。
using・リソース管理
Section titled “using・リソース管理”GCはメモリを管理しますが、ファイルや接続などのリソースは適切なタイミングで解放する必要があります。using は IDisposable.Dispose をスコープ終了時に呼び出します。
using var stream = new MemoryStream();using (var writer = new StreamWriter(stream, leaveOpen: true)){ writer.Write("hello");}stream.Position = 0;using var reader = new StreamReader(stream);Console.WriteLine(reader.ReadToEnd());非同期解放の IAsyncDisposable には await using を使います。using ディレクティブ(名前空間のインポート)とは別の用途です。
async・await・キャンセル
Section titled “async・await・キャンセル”非同期メソッドは一般に Task / Task<T> を返します。await は未完了の処理を待つ間、呼び出し元へ制御を返せます。async 自体は別スレッドを起動しません。
using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(2));try{ Task<int> first = WorkAsync(1, cts.Token); Task<int> second = WorkAsync(2, cts.Token); int[] results = await Task.WhenAll(first, second); Console.WriteLine(string.Join(", ", results));}catch (OperationCanceledException) when (cts.IsCancellationRequested){ Console.WriteLine("Canceled");}
static async Task<int> WorkAsync(int value, CancellationToken cancellationToken){ await Task.Delay(20, cancellationToken); return value * 2;}キャンセルは協調的です。トークンを下位APIに渡し、CPU処理では必要に応じて ThrowIfCancellationRequested() を呼びます。Task.WhenAll はすべてのタスクの完了を待ち、1つの失敗で他のタスクを自動キャンセルしません。
.Result や .Wait() による同期的な待機は避け、呼び出し元まで await を伝播します。async void は通常イベントハンドラーに限定します。I/Oには非同期APIを使い、CPU負荷の高い処理を別スレッドで行いたい場合に Task.Run を検討します。
非同期ストリーム
Section titled “非同期ストリーム”IAsyncEnumerable<T> は要素を非同期で順番に取得するための型です。await foreach で列挙します。
using System.Runtime.CompilerServices;
using var cts = new CancellationTokenSource();await foreach (int value in ReadAsync(cts.Token)) Console.WriteLine(value);
static async IAsyncEnumerable<int> ReadAsync( [EnumeratorCancellation] CancellationToken cancellationToken = default){ for (int i = 0; i < 3; i++) { await Task.Delay(10, cancellationToken); yield return i; }}並行処理・同期
Section titled “並行処理・同期”共有状態の更新は同期します。単純なカウンターには Interlocked、複数の操作をまとめて保護する場合は lock が使えます。
int count = 0;Parallel.For(0, 1_000, _ => Interlocked.Increment(ref count));Console.WriteLine(count);
var gate = new System.Threading.Lock();List<int> values = [];Parallel.For(0, 10, i =>{ lock (gate) { values.Add(i); }});Console.WriteLine(values.Count);lock 内で await は使えません。非同期処理の同時実行数制限には SemaphoreSlim.WaitAsync と finally での Release を使います。ConcurrentDictionary なども、複数操作全体の原子性まで自動的に保証するわけではありません。
Span・Memory
Section titled “Span・Memory”Span<T> は連続したメモリへのビューで、スライス時に要素をコピーしません。ReadOnlySpan<T> はビューを通じた書き込みを禁止します。
int[] values = [1, 2, 3, 4];Span<int> middle = values.AsSpan(1, 2);middle[0] = 20;Console.WriteLine(values[1]);ReadOnlySpan<char> prefix = "hello".AsSpan(0, 2);Console.WriteLine(prefix.ToString());Span<T> は ref struct で、通常のクラスのフィールドに保持できず、await をまたいで使用できません。そのような寿命が必要なら Memory<T> を検討します。読み取り専用ビューでも、元の配列が他から変更される可能性はあります。
標準ライブラリ早引き
Section titled “標準ライブラリ早引き”よく使う型と名前空間をまとめます。
| 用途 | 型・名前空間 |
|---|---|
| 文字列の構築 | System.Text.StringBuilder |
| 正規表現 | System.Text.RegularExpressions.Regex |
| 日時・期間 | DateTimeOffset, DateOnly, TimeOnly, TimeSpan |
| ファイル・パス | System.IO.File, Directory, Path |
| JSON | System.Text.Json.JsonSerializer |
| HTTP | System.Net.Http.HttpClient |
| UUID | Guid |
| 非暗号用途の乱数 | Random.Shared |
| 暗号用途の乱数 | System.Security.Cryptography.RandomNumberGenerator |
| 並行コレクション | System.Collections.Concurrent |
| 非同期キュー | System.Threading.Channels |
| 処理時間の測定 | System.Diagnostics.Stopwatch |
保存・通信する時刻にはUTCやオフセットを明示し、表示時に必要なタイムゾーンへ変換します。
using System.Globalization;
DateTimeOffset now = DateTimeOffset.UtcNow;string encoded = now.ToString("O", CultureInfo.InvariantCulture);DateTimeOffset restored = DateTimeOffset.Parse(encoded, CultureInfo.InvariantCulture);DateOnly date = new(2026, 9, 14);TimeOnly time = new(9, 30);TimeSpan duration = TimeSpan.FromMinutes(45);Console.WriteLine($"{restored:O}, {date:yyyy-MM-dd}, {time:HH:mm}, {duration}");DateTimeOffset が保持するのはオフセットで、タイムゾーンの規則ではありません。夏時間などの変換には TimeZoneInfo を使います。
ファイル・パス
Section titled “ファイル・パス”次の例は一時ファイルを作成し、読み書きした後に削除します。
string path = Path.GetTempFileName();try{ await File.WriteAllTextAsync(path, "hello"); string text = await File.ReadAllTextAsync(path); Console.WriteLine(text); Console.WriteLine(Path.GetFileName(path));}finally{ File.Delete(path);}大きなファイルはストリームや File.ReadLines で逐次処理します。Path.Combine はパス結合のAPIで、外部入力を指定ディレクトリ内に制限する検証ではありません。
標準の System.Text.Json でシリアライズ・デシリアライズできます。
using System.Text.Json;
var options = new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.CamelCase };var person = new Person("Ada", 36);string json = JsonSerializer.Serialize(person, options);Console.WriteLine(json);Person restored = JsonSerializer.Deserialize<Person>(json, options) ?? throw new JsonException("Expected a person.");Console.WriteLine(restored.Name);
public record Person(string Name, int Age);通常の既定設定ではプロパティ名の照合は大文字小文字を区別します。Web用の既定設定とは異なります。デシリアライズ後は業務上必要な値の検証も行います。
次はネットワーク接続が必要な例です。HttpClient はアプリケーションの寿命に合わせて再利用するか、DI環境では IHttpClientFactory で管理します。
using System.Net;
using var handler = new SocketsHttpHandler{ PooledConnectionLifetime = TimeSpan.FromMinutes(2), AutomaticDecompression = DecompressionMethods.All};using var client = new HttpClient(handler) { Timeout = TimeSpan.FromSeconds(10) };using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(5));using var response = await client.GetAsync("https://example.com/", cts.Token);response.EnsureSuccessStatusCode();Console.WriteLine(await response.Content.ReadAsStringAsync(cts.Token));このコンソール例では終了時に破棄します。長時間動くアプリでリクエストごとに生成・破棄する使い方は避けます。実際のアプリではHTTPエラー、接続失敗、タイムアウト・キャンセルを要件に合わせて処理します。
名前空間・プロジェクト・NuGet
Section titled “名前空間・プロジェクト・NuGet”名前空間は型の名前を整理します。以下はクラスライブラリ内のファイル例で、Program.cs の実行例ではありません。
namespace Sample.Library;
public static class Calculator{ public static int Add(int left, int right) => left + right;}ファイルスコープ名前空間は、そのファイル全体に適用されます。using Sample.Library; で型名を短く書けますが、別プロジェクトの利用にはプロジェクト参照も必要です。
CLIコマンド
Section titled “CLIコマンド”以下はチートシート用の独立したソリューションを作る手順です。dotnet new sln は.NET 10では既定で .slnx を作成します。
dotnet new sln -n CheatSheetdotnet new classlib -n Sample.Library --framework net10.0dotnet new console -n Sample.App --framework net10.0dotnet sln CheatSheet.slnx add Sample.Library/Sample.Library.csproj Sample.App/Sample.App.csprojdotnet reference add Sample.Library/Sample.Library.csproj --project Sample.App/Sample.App.csprojdotnet restore CheatSheet.slnxdotnet build CheatSheet.slnxdotnet format CheatSheet.slnx --verify-no-changesdotnet publish Sample.App/Sample.App.csproj -c ReleaseNuGetパッケージは dotnet package add パッケージ名 --project プロジェクトパス で追加します。採用前に公式NuGetページで安定版・対応フレームワークを確認し、更新は dotnet package list --outdated で確認します。再現性が必要ならパッケージバージョンやSDKを固定します。
前節のソリューションにMSTestプロジェクトを追加します。テンプレートがテスト用パッケージを設定します。ここではMSTest 4.4.0を明示します。
dotnet new mstest -n Sample.Tests --framework net10.0dotnet package add MSTest --version 4.4.0 --project Sample.Tests/Sample.Tests.csprojdotnet sln CheatSheet.slnx add Sample.Tests/Sample.Tests.csprojdotnet reference add Sample.Library/Sample.Library.csproj --project Sample.Tests/Sample.Tests.csprojdotnet test CheatSheet.slnxテストコード
Section titled “テストコード”Sample.Library のクラスライブラリに前節の Calculator を配置し、テストプロジェクトの生成されたテストファイルを次のコードで置き換えます。
using Microsoft.VisualStudio.TestTools.UnitTesting;using Sample.Library;
namespace Sample.Tests;
[TestClass]public sealed class CalculatorTests{ [TestMethod] [DataRow(2, 3, 5)] [DataRow(-2, 2, 0)] [DataRow(0, 0, 0)] public void Add_ReturnsSum(int left, int right, int expected) { Assert.AreEqual(expected, Calculator.Add(left, right)); }}このブロックはMSTestプロジェクト用で、単独のコンソールでは実行できません。変更後にもう一度 dotnet test CheatSheet.slnx を実行します。非同期テストは async Task を返し、処理を await します。
C# 14の構文
Section titled “C# 14の構文”C# 14では拡張メンバー、field を使うプロパティ、null条件付き代入などが追加されました。以下は代表的な例です。
Console.WriteLine("hello".IsLong);var person = new Person();person.Name = " Ada ";Console.WriteLine(person.Name);Person? missing = null;missing?.Name = "Grace";Console.WriteLine(nameof(List<>));
public static class TextExtensions{ extension(string text) { public bool IsLong => text.Length >= 5; }}public sealed class Person{ public string Name { get; set => field = value.Trim(); } = "";}null条件付き代入は対象がnullなら右辺も評価しません。field はコンパイラー生成のバッキングフィールドを表します。既存の拡張メソッド構文 this も引き続き使用できます。
Tips・よくあるミス
Section titled “Tips・よくあるミス”- 整数除算で小数が消える:演算前に少なくとも一方を浮動小数点型や
decimalにします。 ==の意味を混同する:通常のクラスは既定で参照比較、recordは値比較、文字列は内容比較です。配列の要素比較にはSequenceEqualを使います。- Nullableの
!に頼る:実行時のnullチェックではありません。入力を検証します。 - LINQを何度も列挙する:遅延実行に注意し、必要なら
ToListなどで結果を保持します。 foreach中に元のList<T>を変更する:RemoveAllや別コレクションへの投影などを使います。- 非同期処理を待ち忘れる:戻されたタスクを
awaitし、例外と完了を観測します。 asyncを並列化と考える:非同期I/OとCPUの並列処理を分けて考えます。Randomをシークレット生成に使う:暗号用途にはRandomNumberGeneratorを使います。DateTime.Nowをタイムゾーン不明のまま保存する:UTCやオフセットを明示します。- recordや
readonlyを深い不変性と考える:参照先のオブジェクトまで不変にはなりません。