C#のソースコードは、一見すると記号やキーワードが多くてとっつきにくく見えますが、実は「using」「namespace」「class」「Main」の4つを押さえれば骨子は理解できます。
本記事では、最小のコンソールアプリから始め、各要素の意味と正しい書き方、.NET 6+での新機能まで丁寧に解説します。
C#プログラムの基本構造の全体像と最小コード例
コンソールアプリの最小構成:using・namespace・class・Mainメソッドの関係
C#のコンソールアプリは、概ね次の4要素で構成されます。
- usingディレクティブ: 型名の短縮(例:
System.Console
をConsole
と書ける) - namespace: コードのグルーピングと衝突回避
- class: 型定義の基本単位(メソッドやフィールドを含む)
- Mainメソッド: 実行が開始されるエントリポイント
これらは「外側から内側へ」包まれるイメージで配置します。
ファイルの先頭にusing
、その下にnamespace
、その中にclass
、そしてMain
メソッドが続きます。
サンプルコードで見るC#の基本構造(初心者向け)
以下は最小構成のコンソールアプリです。
// Program.cs
// 1) usingディレクティブ:System名前空間にある型(Console など)を短く書くため
using System;
// 2) namespace:アプリやライブラリの論理的なグルーピング
namespace DemoApp
{
// 3) class:メソッドやデータを持つ基本単位。エントリポイントを含むことが多い
internal class Program
{
// 4) Mainメソッド:アプリの開始地点。引数を受け取ることも可能
private static void Main(string[] args)
{
Console.WriteLine("Hello, World!");
}
}
}
Hello, World!
上ではusing System;
があるためConsole.WriteLine
を完全修飾名(System.Console.WriteLine
)ではなく短く記述できています。
usingディレクティブの意味と書き方(using)
役割:名前空間の解決と型名の省略
using
ディレクティブは、型をフルネームで書かずに使うための宣言です。
例えばSystem.Console
をConsole
と書けるようにし、可読性を高めます。
コンパイル時に解決されるため、実行オーバーヘッドはありません。
using System; // Console, String, Math などを直接参照可能にする
namespace Samples
{
internal class Greeter
{
internal static void Greet() => Console.WriteLine("Hi");
}
}
書き方・配置ルールとglobal usingの使い方(.NET 6+)
using
ディレクティブは通常、ファイル先頭(namespace
より前)に書きます。
C# 10/.NET 6+では、アプリ全体に適用されるglobal using
が使えます。
// Globals.cs (プロジェクト内で1つ用意して集約するのが定番)
global using System;
global using System.Collections.Generic;
// 通常のusingとの違い:このプロジェクト全体の全ソースファイルで有効
プロジェクト全体で頻用する名前空間(System
, System.Linq
, System.Net.Http
など)はglobal using
で集約すると、各ファイルの先頭がすっきりします。
また、別名をつけるエイリアス指定も可能です。
using IO = System.IO;
namespace Samples
{
internal class Aliases
{
internal static void Show() => Console.WriteLine(typeof(IO.FileInfo).FullName);
}
}
usingディレクティブとusingステートメントの違い
- usingディレクティブ(今回の主題): 名前空間解決のための宣言。ファイル先頭に書くもの。
- usingステートメント: IDisposableを自動破棄するための構文。ブロック内に書くもの。
using System; // ディレクティブ:型名短縮
using System.IO; // ディレクティブ:型名短縮
namespace Samples
{
internal class UsingStatementDemo
{
internal static void ReadFirstLine(string path)
{
// usingステートメント:ブロック終了時にreader.Dispose()が呼ばれる
using var reader = new StreamReader(path);
Console.WriteLine(reader.ReadLine());
}
}
}
よくあるエラーと対処(型が見つからない・曖昧な参照)
- 型が見つからない(CS0246など)
- 例:
The type or namespace name 'HttpClient' could not be found
- 対処:
using System.Net.Http;
を追加、またはプロジェクト参照/パッケージ(System.Net.Http
は通常既定)を確認。完全修飾名(System.Net.Http.HttpClient
)での利用で原因切り分けも可能。
- 例:
- 名前が現在のコンテキストに存在しない(CS0103)
- 例:
The name 'Console' does not exist in the current context
- 対処:
using System;
を追加、またはSystem.Console.WriteLine
と完全修飾で記述。
- 例:
- あいまいな参照(CS0104)
- 例:
'Task' is an ambiguous reference between 'System.Threading.Tasks.Task' and 'MyLib.Task'
- 対処: エイリアス
using TaskEx = System.Threading.Tasks.Task;
を使う、または完全修飾名で記述して衝突を避ける。命名設計の見直しも有効です。
- 例:
namespaceの意味と書き方(namespace)
役割:コードのグルーピングと名前衝突の回避
namespace
は関連する型(クラス、構造体、列挙など)を論理的にまとめ、同名の型が別ライブラリにあっても区別できるようにします。
大規模開発では衝突回避と探索容易性に直結します。
ブロック形式 vs ファイルスコープ形式(C# 10以降)
C# 10からはファイルスコープ形式が追加され、入れ子の波括弧が減って書きやすくなりました。
// ブロック形式(従来)
namespace Company.Product.Feature
{
internal class Worker { }
}
// ファイルスコープ形式(C# 10+)
namespace Company.Product.Feature;
internal class Worker { } // 一段ネストが減る
プロジェクトでどちらかに統一すると読みやすさが向上します。
新規プロジェクト(.NET 6+)ではファイルスコープ形式が初期テンプレートで使われることが多いです。
命名規則とベストプラクティス(企業名.製品名.機能名)
- 形式: 企業名.製品名.サブシステム名.機能名(パスカルケース)
- 例:
Contoso.Sales.Reporting
,Fabrikam.App.Core
- 例:
- 公開ライブラリは企業や組織の一意なプレフィックスで衝突を回避
- フォルダ構成と近い粒度で階層を切ると可読性が上がります
- 略語は最小限に。一般読者が意味を推測できる名前にします
classの意味と定義の基本(class)
クラスの基本構文とアクセス修飾子(public/internal)
クラスはデータ(フィールド/プロパティ)と振る舞い(メソッド)を束ねる単位です。
公開範囲はpublic
(外部から参照可)とinternal
(同一アセンブリ内のみ)を使い分けます。
namespace Company.Product;
// internal(既定):同一アセンブリ内のみ公開
internal class Calculator
{
public int Add(int x, int y) => x + y; // メンバーは public にもできる
}
// public:アセンブリ外にも公開(ライブラリAPIなど)
public class PublicService
{
public string Name { get; }
public PublicService(string name) => Name = name;
}
Programクラスの位置づけと構造化のコツ
コンソールアプリではProgram
クラスがエントリポイント(Main
)を持つのが通例です。
ただしロジックはProgram
に詰め込まず、別クラスへ分離しましょう。
単一責任の原則を意識し、テスト容易性と保守性を高めます。
namespace DemoApp;
internal class Program
{
private static void Main(string[] args)
{
var greeter = new Greeter("World");
Console.WriteLine(greeter.BuildMessage());
}
}
internal class Greeter
{
private readonly string _target;
public Greeter(string target) => _target = target;
public string BuildMessage() => $"Hello, {_target}!";
}
Hello, World!
静的メンバーとインスタンスメンバーの使い分け(基礎)
- 静的(
static
): 共有の機能で状態を持たない(または共有状態)ユーティリティ向け - インスタンス: 個別の状態(プロパティ/フィールド)に依存する処理
namespace Samples;
internal static class StringUtil // 状態を持たないユーティリティは static
{
public static string TrimAndLower(string s) => s?.Trim().ToLowerInvariant() ?? string.Empty;
}
internal class Person // インスタンスごとに異なる状態を持つ
{
public string Name { get; }
public Person(string name) => Name = name;
public string Greet() => $"Hi, I'm {Name}.";
}
internal class Demo
{
public static void Run()
{
Console.WriteLine(StringUtil.TrimAndLower(" Alice "));
var p = new Person("Bob");
Console.WriteLine(p.Greet());
}
}
alice
Hi, I'm Bob.
Mainメソッドの意味と書き方(エントリポイント)
Mainのシグネチャ一覧:void/int・string[] args・async Task
Main
は実行開始地点です。
戻り値や引数の違いでいくつかのシグネチャが許可されています。
- 戻り値なし(既定の終了コード0)
static void Main()
/static void Main(string[] args)
- 戻り値あり(終了コードを返す)
static int Main()
/static int Main(string[] args)
- 非同期(C# 7.1+)
static Task Main()
/static Task Main(string[] args)
static Task<int> Main()
/static Task<int> Main(string[] args)
簡単な例を示します。
namespace EntryPoints;
internal class Program
{
// 1) 引数なし
private static void Main()
{
Console.WriteLine("No args");
}
// 2) 引数あり
// private static void Main(string[] args) { ... }
// 3) 終了コードを返す
// private static int Main(string[] args) => args.Length > 0 ? 0 : 1;
// 4) 非同期(C# 7.1+)
// private static async Task Main(string[] args)
// {
// await Task.Delay(10);
// Console.WriteLine("Async Main");
// }
}
No args
シグネチャは1つに限定してください(同一クラス内に複数同時定義は不可)。
選択したい形以外はコメントアウトや削除を行います。
トップレベルステートメントとの関係(C# 9+/ .NET 6+)
C# 9からは「トップレベルステートメント」によりProgram
クラスやMain
を書く必要がなくなりました。
最小コードで素早く書けます。
// .NET 6+ の新規テンプレートで採用されることが多い
// このファイル全体がエントリポイントとして扱われる
using System;
Console.WriteLine("Hello from top-level");
この書き方でもコンパイラが裏でProgram
クラスとMain
メソッドを生成します。
規模が大きくなる場合や明示性を好む場合は従来のMain
定義に戻す判断も有効です。
コマンドライン引数の受け取り方と戻り値の使い方
引数はstring[] args
で受け取り、戻り値で終了コードを返せます。
次の例は、ファイルが存在すれば0、なければ2を返します。
using System.IO;
namespace CliDemo;
internal class Program
{
private static int Main(string[] args)
{
if (args.Length == 0)
{
Console.Error.WriteLine("Usage: CliDemo <path>");
return 1; // 使い方エラー
}
var path = args[0];
if (File.Exists(path))
{
Console.WriteLine($"Found: {path}");
return 0; // 成功
}
else
{
Console.Error.WriteLine($"Not found: {path}");
return 2; // 見つからない
}
}
}
Usage: CliDemo <path>
上記は引数なしで実行した場合の標準エラー出力例です。
終了コードは環境側(シェル等)から確認できます(例: PowerShell の $LASTEXITCODE
、Linux/Mac の echo $?
など)。
まとめ:C#基本構造のチェックリストとよくあるミス
セミコロン・波括弧・スコープの確認ポイント
- セミコロンの付け忘れ(特に
using
末尾、ステートメント末尾) - 波括弧の対応不一致(
namespace
ブロック形式やclass
の閉じ忘れ) - ファイルスコープ
namespace
を使う場合、末尾はセミコロンであり波括弧は不要
using/namespace/class/Mainの順序と配置のベストプラクティス
using
(→global using
は専用ファイルに集約)をファイル先頭へnamespace
はプロジェクト命名規則に沿って一貫性を保つclass Program
には最小限の起動処理のみ。ロジックは別クラスへ分離Main
のシグネチャは用途に合わせて1つだけを定義(戻り値・非同期の要否を決める)
バージョン差分の注意(file-scoped namespace・global using・top-level)
- ファイルスコープ
namespace
(namespace X;
)はC# 10+ global using
はC# 10+(.NET 6+テンプレでよく使われる)- トップレベルステートメントはC# 9+
- 非同期
Main
はC# 7.1+ - 古いプロジェクトでは使えない構文があるため、プロジェクトのLangVersion/ターゲットフレームワークを確認することが重要です
本記事では、C#の基本構造であるusing
・namespace
・class
・Main
の役割と最新の記法までを体系的に説明しました。
最小の動くコードから始め、プロジェクト規模や要件に合わせてglobal using
やファイルスコープnamespace
、トップレベルステートメントを適切に採用すると、読みやすく保守しやすいコードベースを構築できます。
慣れてきたら、命名規則と責務分割を意識し、Program
に処理を詰め込まない設計を心がけてください。