コンテンツにスキップ

C# チートシート

C# 14・.NET 10をベースにしたチートシートです。日常的に使う構文と標準ライブラリを、説明とサンプルでまとめています。


このページの基準となる安定版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条件付き代入

公式 — C#のバージョン履歴 / 機能別の履歴

ターゲットフレームワークと既定の言語バージョン

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 は受け付ける構文を指定します。対象環境の対応表を超える言語バージョンの使用はサポートされません。

公式 — 言語バージョンの選択

各安定版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系です。

公式 — C#とVisual Studioの履歴

近年の.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の互換性

用途 従来の書き方 新しい書き方 導入版
文字列補間 "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 のターゲットフレームワークを優先します。

Terminal window
dotnet --version
dotnet --list-sdks
dotnet new console -n HelloCSharp --framework net10.0
cd HelloCSharp
dotnet run
dotnet 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;
}

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 とは異なります。


整数同士の除算は整数になります。外部入力の変換には、失敗を戻り値で扱える TryParse が便利です。

Console.WriteLine(5 / 2); // 2
Console.WriteLine(5 / 2.0); // 2.5
if (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 は整数演算・変換のオーバーフロー検出に使います。書式付きの数値や日時を保存・交換する場合は、カルチャーも明示します。


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 で終え、暗黙に次の節へ進みません。


ローカル関数も宣言できます。複数の結果にはタプル、可変長引数には 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<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 は値の妥当性を保証しません。


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でも深い不変性は保証されません。値型にも参照型のフィールドを含められ、その参照はコピー先と共有されます。


クラスが継承できる基底クラスは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 によるメンバーの隠蔽とは異なります。


列挙型は名前付きの整数値を表します。フラグでは各ビットに値を割り当てます。

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 では個別定義がないと認められません。


型パラメーターを使うと、型安全性を保ったまま処理やデータ構造を共通化できます。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と実行条件が異なります。

公式ガイド — LINQクエリ


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 などを検討します。


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 ディレクティブ(名前空間のインポート)とは別の用途です。


非同期メソッドは一般に 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 を検討します。

公式ガイド — 非同期プログラミング


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;
}
}

共有状態の更新は同期します。単純なカウンターには 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<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> を検討します。読み取り専用ビューでも、元の配列が他から変更される可能性はあります。


よく使う型と名前空間をまとめます。

用途 型・名前空間
文字列の構築 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 を使います。

次の例は一時ファイルを作成し、読み書きした後に削除します。

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エラー、接続失敗、タイムアウト・キャンセルを要件に合わせて処理します。

公式ガイド — HttpClientの利用指針


名前空間は型の名前を整理します。以下はクラスライブラリ内のファイル例で、Program.cs の実行例ではありません。

namespace Sample.Library;
public static class Calculator
{
public static int Add(int left, int right) => left + right;
}

ファイルスコープ名前空間は、そのファイル全体に適用されます。using Sample.Library; で型名を短く書けますが、別プロジェクトの利用にはプロジェクト参照も必要です。

以下はチートシート用の独立したソリューションを作る手順です。dotnet new sln は.NET 10では既定で .slnx を作成します。

Terminal window
dotnet new sln -n CheatSheet
dotnet new classlib -n Sample.Library --framework net10.0
dotnet new console -n Sample.App --framework net10.0
dotnet sln CheatSheet.slnx add Sample.Library/Sample.Library.csproj Sample.App/Sample.App.csproj
dotnet reference add Sample.Library/Sample.Library.csproj --project Sample.App/Sample.App.csproj
dotnet restore CheatSheet.slnx
dotnet build CheatSheet.slnx
dotnet format CheatSheet.slnx --verify-no-changes
dotnet publish Sample.App/Sample.App.csproj -c Release

NuGetパッケージは dotnet package add パッケージ名 --project プロジェクトパス で追加します。採用前に公式NuGetページで安定版・対応フレームワークを確認し、更新は dotnet package list --outdated で確認します。再現性が必要ならパッケージバージョンやSDKを固定します。


前節のソリューションにMSTestプロジェクトを追加します。テンプレートがテスト用パッケージを設定します。ここではMSTest 4.4.0を明示します。

Terminal window
dotnet new mstest -n Sample.Tests --framework net10.0
dotnet package add MSTest --version 4.4.0 --project Sample.Tests/Sample.Tests.csproj
dotnet sln CheatSheet.slnx add Sample.Tests/Sample.Tests.csproj
dotnet reference add Sample.Library/Sample.Library.csproj --project Sample.Tests/Sample.Tests.csproj
dotnet test CheatSheet.slnx

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#とMSTest


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 も引き続き使用できます。

公式リファレンス — C# 14の新機能


  • 整数除算で小数が消える:演算前に少なくとも一方を浮動小数点型や decimal にします。
  • == の意味を混同する:通常のクラスは既定で参照比較、recordは値比較、文字列は内容比較です。配列の要素比較には SequenceEqual を使います。
  • Nullableの ! に頼る:実行時のnullチェックではありません。入力を検証します。
  • LINQを何度も列挙する:遅延実行に注意し、必要なら ToList などで結果を保持します。
  • foreach 中に元の List<T> を変更する:RemoveAll や別コレクションへの投影などを使います。
  • 非同期処理を待ち忘れる:戻されたタスクを await し、例外と完了を観測します。
  • async を並列化と考える:非同期I/OとCPUの並列処理を分けて考えます。
  • Random をシークレット生成に使う:暗号用途には RandomNumberGenerator を使います。
  • DateTime.Now をタイムゾーン不明のまま保存する:UTCやオフセットを明示します。
  • recordや readonly を深い不変性と考える:参照先のオブジェクトまで不変にはなりません。