C#は、Microsoftが開発したモダンで多機能なプログラミング言語であり、.NETプラットフォームと共に進化を続けています。
デスクトップアプリ、クラウドサービス、モバイルアプリ、ゲーム開発(Unity)など、その活用範囲は多岐にわたります。
特に近年のアップデートでは、コードの簡潔性と実行パフォーマンスの両立に重点が置かれており、より少ない記述で安全かつ高速なプログラムを記述できるようになりました。
本記事では、C#の基本構文から最新の機能まで、実務で頻繁に使用する項目を網羅的に解説します。
日々の開発におけるリファレンスとして、あるいは最新仕様のキャッチアップとしてご活用ください。
基本構文と変数宣言
C#の基本は、型安全な変数の宣言と制御構造です。
現代的なC#では、型推論を積極的に活用しつつ、null安全を意識した記述が求められます。
変数と定数
変数の宣言には、具体的な型を明示する方法と、コンパイラに型を推論させるvarを使用する方法があります。
// 明示的な型宣言
int count = 10;
string message = "Hello, C#";
// 型推論(右辺から型が明らかな場合に使用)
var list = new List<string>();
// 定数の宣言(コンパイル時定数)
const double Pi = 3.14159;
// 読み取り専用(実行時定数)
readonly int MaxRuntimeValue = count * 2;
Console.WriteLine($"Count: {count}, Message: {message}, Pi: {Pi}");
Count: 10, Message: "Hello, C#", Pi: 3.14159
組み込みデータ型
C#には多様な組み込み型が存在します。
特に数値型や文字列型の扱いに慣れることが重要です。
| 分類 | 型名 | 説明 |
|---|---|---|
| 整数 | int, long, short, byte | 符号あり/なしの整数 |
| 浮動小数点 | double, float, decimal | 金銭計算には decimal を推奨 |
| 真偽値 | bool | true または false |
| 文字・文字列 | char, string | 文字列はイミュータブル(不変) |
| オブジェクト | object | すべての型の基本クラス |
文字列操作
モダンなC#では、文字列補間(String Interpolation)を使用して、直感的に文字列を構築します。
string name = "Alice";
int age = 25;
// 文字列補間
string greeting = $"Name: {name}, Age: {age}";
// 生文字列リテラル (C# 11以降)
// 改行やダブルクォートをそのまま記述可能
string xml = """
<user id="1">
<name>Bob</name>
</user>
""";
Console.WriteLine(greeting);
Console.WriteLine(xml);
制御構造とパターンマッチング
プログラムの流れを制御するための構文は、従来の if や for に加え、強力なパターンマッチングが導入されています。
条件分岐とループ
標準的な条件分岐とループ処理の記述例です。
// 条件分岐
if (age >= 20)
{
Console.WriteLine("Adult");
}
else
{
Console.WriteLine("Minor");
}
// 三項演算子
string status = (age >= 20) ? "Adult" : "Minor";
// ループ処理
var numbers = new[] { 1, 2, 3, 4, 5 };
foreach (var num in numbers)
{
Console.WriteLine(num);
}
switch式(Modern Switch)
従来の switch 文よりも簡潔に記述できる switch 式が推奨されます。
int day = 3;
string dayName = day switch
{
1 => "Monday",
2 => "Tuesday",
3 => "Wednesday",
_ => "Unknown" // デフォルトケース
};
// プロパティパターンを利用したマッチング
object obj = "Hello";
string result = obj switch
{
string s when s.Length > 5 => "Long string",
string s => "Short string",
int i => $"Number: {i}",
null => "It's null",
_ => "Other type"
};
Console.WriteLine(dayName);
Console.WriteLine(result);
Wednesday
Short string
オブジェクト指向プログラミングの進化
C#はクラスベースのオブジェクト指向言語ですが、近年はデータの不変性(Immutability)を重視した機能が追加されています。
クラスとレコード
record 型は、データの保持に特化した型であり、値ベースの比較を自動で行います。
// 従来のクラス定義
public class Person
{
public string Name { get; set; }
public int Age { get; set; }
}
// レコード型(C# 9.0以降)
// 位置指定パラメータにより、1行で定義可能
public record User(string Name, int Age);
// 使用例
var user1 = new User("Alice", 30);
var user2 = user1 with { Age = 31 }; // 非破壊的変更
Console.WriteLine(user1);
Console.WriteLine(user2);
Console.WriteLine(user1 == new User("Alice", 30)); // True (値比較)
プライマリコンストラクタ (C# 12)
クラス定義の際に引数を直接受け取ることができるようになり、ボイラープレートコードが削減されました。
// クラス名に続けて引数を記述
public class Service(string connectionString, ILogger logger)
{
public void Connect()
{
logger.Log($"Connecting to {connectionString}");
}
}
インターフェースと抽象クラス
インターフェースは契約を定義し、抽象クラスは共通の実装を提供します。
public interface IRepository
{
void Save(string data);
// インターフェースのデフォルト実装(C# 8.0以降)
void Log(string message) => Console.WriteLine($"Log: {message}");
}
public abstract class BaseEntity
{
public Guid Id { get; init; } = Guid.NewGuid();
public abstract void Process();
}
null許容参照型と安全な設計
C# 8.0以降、NullReferenceExceptionを防ぐための「null許容参照型」が導入されました。
nullの制御
型名の後ろに ? を付けることで、その変数が null になる可能性があることを明示します。
string? nullableString = null;
string nonNullableString = "Hello";
// null条件演算子
int? length = nullableString?.Length;
// null合体演算子 (nullの場合のデフォルト値指定)
string display = nullableString ?? "Guest";
// null合体割当演算子
nullableString ??= "Default Value";
// null免除演算子(!)
// コンパイラに「ここは絶対にnullではない」と伝える
string forced = nullableString!;
コレクションとLINQ
データの集合を操作する際、C#ではLINQ(Language Integrated Query)を用いることで、宣言的で読みやすいコードが書けます。
主要なコレクション
実務では主に List<T> と Dictionary<TKey, TValue> を使用します。
// リスト
var names = new List<string> { "Alice", "Bob", "Charlie" };
names.Add("Dave");
// 辞書
var scores = new Dictionary<string, int>
{
["Alice"] = 100,
["Bob"] = 85
};
// コレクション式 (C# 12)
int[] numbers = [1, 2, 3, 4, 5];
List<int> moreNumbers = [6, 7, 8];
LINQによるデータ操作
LINQを使うことで、フィルタリング、変換、集計をメソッドチェーンで記述できます。
using System.Linq;
var source = new List<int> { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
// フィルタリングと変換
var result = source
.Where(n => n % 2 == 0) // 偶数のみ抽出
.Select(n => n * n) // 2乗する
.OrderByDescending(n => n) // 降順ソート
.ToList();
// 単一要素の取得
int first = source.FirstOrDefault(n => n > 5);
// 集計
int sum = source.Sum();
double average = source.Average();
Console.WriteLine($"Result: {string.Join(", ", result)}");
Console.WriteLine($"Sum: {sum}");
Result: 100, 64, 36, 16, 4
Sum: 55
非同期プログラミング (async/await)
I/O待ちが発生する処理(API通信、ファイル操作、データベースアクセス)では、async/await による非同期プログラミングが必須です。
非同期メソッドの実装
Task または Task<T> を戻り値として定義します。
public async Task<string> FetchDataAsync(string url)
{
using var client = new HttpClient();
try
{
// 非同期にデータを取得(スレッドをブロックしない)
string content = await client.GetStringAsync(url);
return content;
}
catch (HttpRequestException ex)
{
return $"Error: {ex.Message}";
}
}
// 呼び出し側
public async Task RunProcess()
{
string data = await FetchDataAsync("https://api.example.com/info");
Console.WriteLine(data);
}
並列実行
複数のタスクを同時に開始し、すべてが終わるまで待機する場合は Task.WhenAll を使用します。
var task1 = FetchDataAsync("url1");
var task2 = FetchDataAsync("url2");
// 同時に開始し、両方の完了を待つ
string[] results = await Task.WhenAll(task1, task2);
例外処理とリソース管理
堅牢なアプリケーションを作成するためには、適切なエラーハンドリングとメモリ管理が必要です。
例外処理 (try-catch-finally)
特定の例外をキャッチし、ログ出力やリカバリを行います。
try
{
int result = 10 / int.Parse("0");
}
catch (DivideByZeroException ex)
{
Console.WriteLine("Cannot divide by zero.");
}
catch (FormatException ex)
{
Console.WriteLine("Invalid input format.");
}
catch (Exception ex)
{
// 予期しない例外のキャッチ
Console.WriteLine($"Unexpected error: {ex.Message}");
throw; // 再スロー
}
finally
{
// 成功・失敗に関わらず実行される
Console.WriteLine("Cleanup operation.");
}
using ステートメント
ファイルハンドルやネットワーク接続など、IDisposable を実装したリソースは using を用いて確実に解放します。
// 従来の using (スコープが明確)
using (var stream = new FileStream("test.txt", FileMode.Open))
{
// 処理
}
// using 宣言 (C# 8.0以降、現在のブロックを抜ける際に解放)
using var writer = new StreamWriter("output.txt");
writer.WriteLine("Hello World");
依存性の注入 (DI) と設定管理
現代的な.NETアプリケーション(ASP.NET Coreなど)では、依存性の注入(Dependency Injection)が標準的に利用されます。
サービスの登録と利用
インターフェースとその実装を分離することで、テストが容易で疎結合な設計が可能になります。
// サービスの定義
public interface IMessageService { string GetMessage(); }
public class EmailService : IMessageService { public string GetMessage() => "Email"; }
// DIコンテナへの登録 (Startup.csやProgram.cs)
// builder.Services.AddScoped<IMessageService, EmailService>();
// クラスでの利用(コンストラクタ注入)
public class HomeController(IMessageService messageService)
{
public void Index()
{
var msg = messageService.GetMessage();
Console.WriteLine(msg);
}
}
高度な最新機能
C#の最新バージョンでは、開発効率をさらに高めるための高度な機能が追加されています。
ジェネリクスと制約
型をパラメータ化することで、再利用性の高いクラスを作成できます。
public class DataStore<T> where T : class, new()
{
private List<T> _items = new();
public void Add(T item) => _items.Add(item);
public T CreateAndAdd()
{
var item = new T();
_items.Add(item);
return item;
}
}
拡張メソッド
既存の型に、ソースコードを修正することなく新しいメソッドを追加できます。
public static class StringExtensions
{
public static bool IsNullOrEmpty(this string? value)
{
return string.IsNullOrEmpty(value);
}
}
// 使用例
string? myStr = null;
if (myStr.IsNullOrEmpty())
{
Console.WriteLine("Empty!");
}
Span<T> とメモリ効率
高パフォーマンスが求められるシナリオでは、Span<T> を使用してヒープ割り当てを抑えたメモリ操作を行います。
// 配列の一部をコピーせずに参照
int[] array = { 1, 2, 3, 4, 5 };
Span<int> slice = array.AsSpan(1, 3); // { 2, 3, 4 }
slice[0] = 10; // 元の配列も書き換わる
Console.WriteLine(array[1]); // 10
まとめ
C#は、誕生当初のJavaに近い言語仕様から、関数型プログラミングの要素や高度なメモリ管理機能を取り込み、「書きやすさ」と「速さ」を極めた言語へと進化しました。
本記事で紹介したチートシートのポイントを振り返ります。
- 基本構文:
varや文字列補間を使い、簡潔に記述する。 - 最新の型システム:
recordやプライマリコンストラクタを活用してボイラープレートを排除する。 - null安全:null許容参照型を有効にし、実行時エラーを未然に防ぐ。
- 非同期処理:
async/awaitを正しく使い、レスポンスの良いアプリを設計する。 - LINQ:コレクション操作をメソッドチェーンで効率化する。
C#の学習において最も重要なのは、「新しいバージョンの機能を積極的に使うこと」です。
古い書き方でも動作はしますが、最新の構文を使うことで、コードの可読性とメンテナンス性は劇的に向上します。
本チートシートを参考に、モダンなC#開発を実践していきましょう。






