C#コマンドライン入門|引数の受け取り方から実行・オプション解析まで初心者向けに解説

はじめに

C#でコンソールアプリを作るとき、避けて通れないのが「コマンドライン引数」です。たとえば、次のようにプログラムを実行するとします。

Bash
dotnet run -- Tanaka

このとき Tanaka という値をC#プログラム側で受け取り、名前の表示、ファイルの読み込み、処理モードの切り替えなどに使えます。

C#コマンドラインアプリは、GUIアプリよりもシンプルに作れるため、学習用の小さなプログラム、社内ツール、自動処理、バッチ処理、ファイル変換ツールなどに向いています。この記事では、C#でコマンドライン引数を受け取る基本から、dotnet runでの実行方法、オプション解析、System.CommandLineを使った本格的な実装まで、初心者向けに順番に解説します。

1. C#のコマンドライン引数とは?まず押さえる基本

1-1. コマンドライン引数とは何か

コマンドライン引数とは、プログラムを起動するときに一緒に渡す文字列のことです。

たとえば、次のコマンドを見てください。

Bash
myapp.exe apple banana orange

この場合、applebananaorange がコマンドライン引数です。C#では、これらの値を string[] args という文字列配列として受け取れます。

C#
static void Main(string[] args)
{
Console.WriteLine(args[0]); // apple
Console.WriteLine(args[1]); // banana
Console.WriteLine(args[2]); // orange
}

コマンドライン引数は、ユーザーがプログラムの動作を外から指定するための仕組みです。プログラム内に値を直接書き込まず、実行時に変更できる点が大きなメリットです。

1-2. C#でコマンドライン引数を使う場面

C#でコマンドライン引数を使う代表的な場面は、次のようなケースです。

用途実行例
名前を渡すmyapp.exe Taro
ファイルパスを渡すmyapp.exe input.txt
数値を渡すmyapp.exe 100 200
処理モードを指定するmyapp.exe --mode fast
フラグを指定するmyapp.exe --verbose

たとえば、ファイル変換ツールなら次のように使えます。

Bash
convert.exe input.csv output.json

この場合、input.csv を読み込み、output.json に変換結果を書き出す、という処理をC#側で実装できます。

1-3. 引数・オプション・コマンドの違い

初心者が混乱しやすいのが、「引数」「オプション」「コマンド」の違いです。

種類意味
引数input.txtプログラムに渡す値
オプション--name Taro名前付きで渡す設定値
フラグ--verbosetrue/falseを切り替える指定
コマンドadddelete実行する処理の種類

たとえば、次のコマンドを考えます。

Bash
todo.exe add "買い物に行く" --priority high

この場合、add はコマンド、"買い物に行く" は引数、--priority high はオプションです。

小さなC#コマンドラインアプリなら args を順番に読むだけでも十分です。一方で、複数のオプションやサブコマンドを扱う場合は、後半で紹介する System.CommandLine のようなライブラリを使うと管理しやすくなります。

1-4. 初心者がつまずきやすいポイント

C#コマンドラインで初心者がよくつまずくポイントは、主に次の5つです。

1つ目は、args[0] をいきなり参照してエラーになることです。引数が渡されていない状態で args[0] を読むと、配列の範囲外アクセスになります。

C#
Console.WriteLine(args[0]); // 引数がないとエラー

2つ目は、スペースを含む文字列を引用符で囲まないことです。

Bash
myapp.exe Tokyo Station

この場合、TokyoStation は別々の引数になります。1つの文字列として渡したい場合は、次のようにします。

Bash
myapp.exe "Tokyo Station"

3つ目は、dotnet run に渡す引数と、自分のアプリに渡す引数を混同することです。dotnet run では、アプリ側に渡したい引数を -- の後ろに書くのが基本です。Microsoft Learnでも、dotnet run の引数とアプリケーション引数を分けるために -- を使うと説明されています。

Bash
dotnet run -- apple banana

4つ目は、数値も最初は文字列として受け取る点です。100 と入力しても、C#側では "100" という文字列なので、計算に使うには int.Parseint.TryParse で変換する必要があります。

5つ目は、Windows、macOS、Linux、PowerShell、コマンドプロンプト、Bashなど、実行環境によって引用符やパスの扱いが少し違うことです。まずは基本の受け取り方を理解し、そのあと自分の利用環境で動作確認することが大切です。

2. C#でコマンドライン引数を受け取る方法

2-1. Mainメソッドのstring[] argsで受け取る

C#でコマンドライン引数を受け取る基本形は、Main メソッドに string[] args を書く方法です。

C#
using System;

class Program
{
static void Main(string[] args)
{
Console.WriteLine("引数の数: " + args.Length);

foreach (string arg in args)
{
Console.WriteLine(arg);
}
}
}

実行例は次のとおりです。

Bash
dotnet run -- apple banana orange

出力例です。

引数の数: 3
apple
banana
orange

args は文字列配列です。つまり、1つ目の引数は args[0]、2つ目の引数は args[1]、3つ目の引数は args[2] で取得できます。

2-2. argsの中身を確認する基本コード

まずは、受け取った引数をすべて表示するコードを書いてみましょう。

C#
using System;

class Program
{
static void Main(string[] args)
{
for (int i = 0; i < args.Length; i++)
{
Console.WriteLine($"args[{i}] = {args[i]}");
}
}
}

次のように実行します。

Bash
dotnet run -- red green blue

出力は次のようになります。

args[0] = red
args[1] = green
args[2] = blue

コマンドライン引数の学習では、まず args に何が入っているかを表示するのが一番わかりやすい確認方法です。

2-3. args.Lengthで引数の有無を判定する

引数を安全に扱うには、必ず args.Length を確認します。

C#
using System;

class Program
{
static void Main(string[] args)
{
if (args.Length == 0)
{
Console.WriteLine("引数を指定してください。");
return;
}

Console.WriteLine($"最初の引数: {args[0]}");
}
}

このように書けば、引数がないときに args[0] を読んでエラーになることを防げます。

複数の引数が必要な場合は、必要数をチェックします。

C#
if (args.Length < 2)
{
Console.WriteLine("使い方: myapp <入力ファイル> <出力ファイル>");
return;
}

string inputPath = args[0];
string outputPath = args[1];

初心者のうちは、「args を使う前に args.Length を確認する」と覚えておきましょう。

2-4. トップレベルステートメントでargsを使う方法

最近のC#では、class Programstatic void Main を書かずに、トップレベルステートメントでプログラムを書けます。

C#
Console.WriteLine($"引数の数: {args.Length}");

foreach (string arg in args)
{
Console.WriteLine(arg);
}

これだけで、args を使えます。Program.cs にこのコードを書き、次のように実行します。

Bash
dotnet run -- hello world

出力例です。

引数の数: 2
hello
world

学習中は Main メソッド形式のほうが仕組みを理解しやすいですが、短いサンプルや小さなツールではトップレベルステートメントも便利です。

2-5. Environment.GetCommandLineArgsとの違い

C#では、Environment.GetCommandLineArgs() を使ってコマンドライン引数を取得することもできます。

C#
string[] values = Environment.GetCommandLineArgs();

foreach (string value in values)
{
Console.WriteLine(value);
}

string[] args との大きな違いは、Environment.GetCommandLineArgs() には実行ファイル名や実行パスに相当する値も含まれることです。

たとえば、通常の args では次のようになります。

args[0] = apple
args[1] = banana

一方、Environment.GetCommandLineArgs() では、先頭にプログラム自身のパスが入ることがあります。

values[0] = C:\sample\myapp.exe
values[1] = apple
values[2] = banana

通常の引数処理では string[] args を使えば十分です。実行ファイル自身のパスも含めて確認したい場合に、Environment.GetCommandLineArgs() を使うとよいでしょう。

3. C#プログラムをコマンドラインから実行する手順

3-1. .NETプロジェクトを作成する

まずはC#のコンソールアプリを作成します。.NET SDKがインストールされている状態で、ターミナルやコマンドプロンプトを開き、次のコマンドを実行します。

Bash
dotnet new console -n CommandLineSample

作成されたフォルダに移動します。

Bash
cd CommandLineSample

Program.cs を開くと、コンソールアプリのコードが入っています。ここにコマンドライン引数を受け取るコードを書いていきます。

.NETはWindows、Linux、macOSで利用できるクロスプラットフォームな開発環境として提供されています。

3-2. dotnet runで実行する

作成したプロジェクトは、次のコマンドで実行できます。

Bash
dotnet run

dotnet run は、ソースコードをビルドしてから実行してくれる便利なコマンドです。Microsoft Learnでは、dotnet run は明示的なコンパイルや起動コマンドなしでソースコードを実行するコマンドと説明されています。

たとえば Program.cs を次のようにします。

C#
Console.WriteLine("Hello, command line!");

実行すると、次のように表示されます。

Hello, command line!

3-3. dotnet runで引数を渡す方法

dotnet run でC#プログラムに引数を渡す場合は、基本的に -- の後ろに引数を書きます。

Bash
dotnet run -- apple banana

Program.cs を次のようにします。

C#
foreach (string arg in args)
{
Console.WriteLine(arg);
}

出力例です。

apple
banana

重要なのは、-- より前は dotnet run に対する指定、-- より後ろは自分のC#アプリに渡す指定、という点です。

たとえば、Release構成で実行しつつ、アプリには Taro を渡したい場合は次のようにします。

Bash
dotnet run --configuration Release -- Taro

このとき、--configuration Releasedotnet run の設定で、Taro はC#アプリ側の args に入ります。

3-4. exeファイルに引数を渡して実行する方法

ビルド済みの実行ファイルに直接引数を渡すこともできます。

まずビルドします。

Bash
dotnet build

Windowsの場合、出力フォルダに移動して、次のように実行できます。

Bash
bin\Debug\net8.0\CommandLineSample.exe apple banana

macOSやLinuxでは、DLLを dotnet で実行する形になることもあります。

Bash
dotnet bin/Debug/net8.0/CommandLineSample.dll apple banana

配布用に発行した場合は、発行先の実行ファイルに引数を渡します。

Bash
CommandLineSample.exe input.txt output.txt

dotnet run は開発中に便利ですが、本番利用や配布では dotnet publish で発行した成果物を実行するのが一般的です。

3-5. Visual Studioでコマンドライン引数を設定する方法

Visual Studioでデバッグ実行するときにも、コマンドライン引数を設定できます。

一般的な流れは次のとおりです。

  1. プロジェクトを右クリックする

  2. 「プロパティ」を開く

  3. 「デバッグ」関連の設定を開く

  4. コマンドライン引数の欄に値を入力する

  5. F5キーまたはデバッグ実行する

たとえば、コマンドライン引数に次のように入力します。

apple banana "Tokyo Station"

この状態でデバッグ実行すると、args には次のように入ります。

args[0] = apple
args[1] = banana
args[2] = Tokyo Station

Visual Studioのバージョンによって画面名は多少異なりますが、「デバッグ」「起動プロファイル」「コマンドライン引数」などの項目を探すと設定できます。

4. コマンドライン引数の扱い方をサンプルで理解する

4-1. 文字列の引数を受け取る

もっとも基本的なのは、文字列を受け取って表示する処理です。

C#
if (args.Length == 0)
{
Console.WriteLine("名前を指定してください。");
return;
}

string name = args[0];
Console.WriteLine($"こんにちは、{name}さん!");

実行例です。

Bash
dotnet run -- Taro

出力例です。

こんにちは、Taroさん!

コマンドライン引数はすべて文字列として渡されるため、名前、メッセージ、ファイル名などの文字列はそのまま扱えます。

4-2. 数値の引数を受け取って変換する

数値を受け取る場合も、最初は文字列です。計算に使うには変換が必要です。

C#
if (args.Length < 2)
{
Console.WriteLine("使い方: myapp <数値1> <数値2>");
return;
}

int a = int.Parse(args[0]);
int b = int.Parse(args[1]);

Console.WriteLine($"合計: {a + b}");

実行例です。

Bash
dotnet run -- 10 20

出力例です。

合計: 30

ただし、int.Parse は変換できない文字列が来ると例外になります。実用的には int.TryParse を使うほうが安全です。

C#
if (!int.TryParse(args[0], out int a))
{
Console.WriteLine("1つ目の引数は数値で指定してください。");
return;
}

if (!int.TryParse(args[1], out int b))
{
Console.WriteLine("2つ目の引数は数値で指定してください。");
return;
}

Console.WriteLine($"合計: {a + b}");

4-3. 複数の引数を順番に処理する

複数の引数をまとめて処理したい場合は、foreach が便利です。

C#
if (args.Length == 0)
{
Console.WriteLine("1つ以上の値を指定してください。");
return;
}

foreach (string item in args)
{
Console.WriteLine($"受け取った値: {item}");
}

実行例です。

Bash
dotnet run -- apple banana orange

出力例です。

受け取った値: apple
受け取った値: banana
受け取った値: orange

順番に意味がある場合は、インデックスで取り出します。

C#
string inputFile = args[0];
string outputFile = args[1];

Console.WriteLine($"入力: {inputFile}");
Console.WriteLine($"出力: {outputFile}");

このような設計では、ユーザーに使い方を明示することが重要です。

使い方: myapp <入力ファイル> <出力ファイル>

4-4. スペースを含む文字列を引数として渡す

スペースを含む文字列を1つの引数として渡すには、引用符で囲みます。

Bash
dotnet run -- "Tokyo Station"

C#側では、args[0]Tokyo Station が入ります。

C#
Console.WriteLine(args[0]);

出力例です。

Tokyo Station

引用符で囲まない場合は、次のように別々の引数として扱われます。

Bash
dotnet run -- Tokyo Station

この場合は次のようになります。

args[0] = Tokyo
args[1] = Station

ファイルパスにもスペースが含まれることがあります。

Bash
dotnet run -- "C:\Users\Taro\My Documents\input.txt"

Windowsのパスやフォルダ名にはスペースが含まれることがあるため、パスを引数で渡すときは引用符で囲む習慣をつけると安全です。

4-5. ファイルパスを引数として受け取る

ファイルパスを受け取り、存在するか確認するサンプルです。

C#
if (args.Length == 0)
{
Console.WriteLine("ファイルパスを指定してください。");
return;
}

string path = args[0];

if (!File.Exists(path))
{
Console.WriteLine($"ファイルが見つかりません: {path}");
return;
}

string text = File.ReadAllText(path);
Console.WriteLine(text);

実行例です。

Bash
dotnet run -- input.txt

スペースを含むパスの場合は、次のようにします。

Bash
dotnet run -- "C:\sample folder\input.txt"

ファイルを扱うC#コマンドラインアプリでは、次のチェックを入れると実用性が上がります。

チェック項目内容
引数があるかargs.Length で確認
ファイルが存在するかFile.Exists で確認
読み込み可能か例外処理で確認
パスが空でないかstring.IsNullOrWhiteSpace で確認

5. C#でコマンドラインオプションを解析する基本

5-1. --nameや-fのようなオプションとは

コマンドラインツールでは、次のような形式をよく使います。

Bash
myapp.exe --name Taro --age 20

この --name--age がオプションです。短い形式として、次のように書くこともあります。

Bash
myapp.exe -n Taro -a 20

一般的には、次のように使い分けます。

形式特徴
ロングオプション--name意味がわかりやすい
ショートオプション-n短く書ける
フラグ--verbose指定されたかどうかを見る
値付きオプション--file input.txtオプション名と値をセットで扱う

単純な引数だけなら args[0]args[1] で十分ですが、引数の数が増えると順番に依存する設計は使いにくくなります。そこで、--name のような名前付きオプションを使うと、わかりやすいコマンドラインになります。

5-2. 手動でオプションを解析する方法

まずは、手動で --name オプションを解析してみます。

C#
string? name = null;

for (int i = 0; i < args.Length; i++)
{
if (args[i] == "--name" && i + 1 < args.Length)
{
name = args[i + 1];
i++;
}
}

if (name == null)
{
Console.WriteLine("--name を指定してください。");
return;
}

Console.WriteLine($"こんにちは、{name}さん!");

実行例です。

Bash
dotnet run -- --name Taro

出力例です。

こんにちは、Taroさん!

ポイントは、--name の次の要素を値として読むことです。

args[0] = --name
args[1] = Taro

ただし、手動解析では、値の指定漏れ、不正なオプション、重複指定などを自分で処理する必要があります。

5-3. オプション名と値をDictionaryで管理する

オプションが増える場合は、Dictionary<string, string> に入れると扱いやすくなります。

C#
var options = new Dictionary<string, string>();

for (int i = 0; i < args.Length; i++)
{
string key = args[i];

if (key.StartsWith("--"))
{
if (i + 1 < args.Length && !args[i + 1].StartsWith("--"))
{
options[key] = args[i + 1];
i++;
}
else
{
Console.WriteLine($"{key} の値が指定されていません。");
return;
}
}
}

if (options.TryGetValue("--name", out string? name))
{
Console.WriteLine($"名前: {name}");
}

if (options.TryGetValue("--mode", out string? mode))
{
Console.WriteLine($"モード: {mode}");
}

実行例です。

Bash
dotnet run -- --name Taro --mode fast

出力例です。

名前: Taro
モード: fast

小さなツールなら、この程度の手動解析でも十分です。ただし、サブコマンドや必須チェック、ヘルプ表示まで実装する場合は、ライブラリの利用を検討しましょう。

5-4. フラグ形式のオプションを扱う

フラグとは、値を持たず、指定されたかどうかだけを見るオプションです。

Bash
myapp.exe --verbose

C#では、次のように判定できます。

C#
bool verbose = args.Contains("--verbose");

if (verbose)
{
Console.WriteLine("詳細ログを表示します。");
}
else
{
Console.WriteLine("通常モードで実行します。");
}

実行例です。

Bash
dotnet run -- --verbose

出力例です。

詳細ログを表示します。

フラグは、次のような用途でよく使います。

フラグ用途
--verbose詳細ログを表示する
--dry-run実際には変更せず確認だけする
--force確認なしで実行する
--help使い方を表示する

5-5. 必須オプションと任意オプションを判定する

実用的なC#コマンドラインアプリでは、「必須オプション」と「任意オプション」を分けて考えます。

たとえば、ファイル処理ツールなら --input は必須、--verbose は任意という設計が考えられます。

C#
string? input = null;
bool verbose = false;

for (int i = 0; i < args.Length; i++)
{
switch (args[i])
{
case "--input":
if (i + 1 >= args.Length)
{
Console.WriteLine("--input の値を指定してください。");
return;
}
input = args[++i];
break;

case "--verbose":
verbose = true;
break;

default:
Console.WriteLine($"不明なオプションです: {args[i]}");
return;
}
}

if (input == null)
{
Console.WriteLine("必須オプション --input を指定してください。");
Console.WriteLine("使い方: myapp --input <ファイルパス> [--verbose]");
return;
}

Console.WriteLine($"入力ファイル: {input}");

if (verbose)
{
Console.WriteLine("詳細ログ: 有効");
}

実行例です。

Bash
dotnet run -- --input data.txt --verbose

手動解析では、switch 文を使うと読みやすくなります。

6. System.CommandLineを使った本格的なオプション解析

6-1. System.CommandLineとは

System.CommandLine は、C#で本格的なコマンドラインアプリを作るためのライブラリです。コマンドライン入力の解析、ヘルプ表示、オプション定義など、CLIツールでよく必要になる機能を提供します。Microsoft Learnでも、System.CommandLine はコマンドライン入力の解析やヘルプテキストの表示など、コマンドラインアプリに必要な機能を提供するライブラリと説明されています。

手動解析では次のような処理を自分で書く必要があります。

--name の値を読む
--help を表示する
不正なオプションを検出する
必須オプションをチェックする
型変換する
サブコマンドを処理する

System.CommandLine を使うと、これらを整理して実装できます。

6-2. System.CommandLineの導入方法

プロジェクトに System.CommandLine を追加します。

Bash
dotnet add package System.CommandLine

.NET 10以降の環境では、次の形式も使えます。

Bash
dotnet package add System.CommandLine

Microsoft Learnのチュートリアルでも、System.CommandLine パッケージを追加する手順としてこれらのコマンドが紹介されています。

導入後、C#コードで次の名前空間を使います。

C#
using System.CommandLine;

6-3. Optionを定義して値を受け取る

System.CommandLine では、Option<T> を使ってオプションを定義します。

C#
using System.CommandLine;

Option<string> nameOption = new("--name")
{
Description = "表示する名前"
};

RootCommand rootCommand = new("名前を表示するサンプル");
rootCommand.Options.Add(nameOption);

rootCommand.SetAction(parseResult =>
{
string? name = parseResult.GetValue(nameOption);

if (string.IsNullOrWhiteSpace(name))
{
Console.WriteLine("--name を指定してください。");
return 1;
}

Console.WriteLine($"こんにちは、{name}さん!");
return 0;
});

return rootCommand.Parse(args).Invoke();

実行例です。

Bash
dotnet run -- --name Taro

出力例です。

こんにちは、Taroさん!

Option<int> を使えば、数値として受け取ることもできます。

C#
Option<int> countOption = new("--count")
{
Description = "繰り返し回数"
};

手動で int.Parse しなくても、ライブラリ側で型に応じた解析をしてくれる点が便利です。

6-4. help表示を自動生成する

System.CommandLine の大きなメリットは、ヘルプ表示を自動生成できることです。

たとえば、次のように実行します。

Bash
dotnet run -- --help

すると、定義したコマンドやオプションに基づいて、使い方が表示されます。Microsoft Learnのチュートリアルでも、RootCommand は既定でヘルプオプションやバージョンオプションを提供し、--help による表示ができることが示されています。

ヘルプ表示は、ユーザーにとって非常に重要です。特に配布するコマンドラインツールでは、次の情報があると使いやすくなります。

項目
概要何をするツールか
使い方myapp --file input.txt
オプション一覧--file--mode
必須か任意か--file は必須
具体例実行コマンドのサンプル

自分で --help を実装することもできますが、オプションが増えるとメンテナンスが大変です。System.CommandLine を使えば、コードで定義した内容をもとにヘルプを生成できるため、実装と説明のズレを減らせます。

6-5. 複雑なコマンドラインツールを作るときの設計ポイント

複雑なC#コマンドラインツールでは、最初にコマンド構造を決めることが大切です。

たとえば、TODO管理ツールなら次のような構造が考えられます。

Bash
todo add "牛乳を買う"
todo list
todo done 1
todo delete 1

この場合、addlistdonedelete はサブコマンドです。

設計するときは、次の点を意識しましょう。

ポイント内容
1コマンド1役割add は追加、delete は削除
必須値を明確にするファイルパスやIDなど
オプション名をわかりやすくする--input--output
短縮形は必要なものだけ-i-o など
ヘルプを必ず用意する--help で確認できるようにする
終了コードを返す成功は 0、失敗は 1 など

最初から大きな設計にする必要はありません。初心者は、まず args で単純な引数を扱い、次に手動で --name のようなオプションを解析し、必要になったら System.CommandLine を導入する流れがおすすめです。

7. エラー処理と入力チェックの実装方法

7-1. 引数が足りない場合の処理

コマンドラインアプリでは、引数不足への対応が必須です。

悪い例です。

C#
string input = args[0];
string output = args[1];

引数が足りないと、実行時エラーになります。

良い例です。

C#
if (args.Length < 2)
{
Console.WriteLine("使い方: myapp <入力ファイル> <出力ファイル>");
return;
}

string input = args[0];
string output = args[1];

ユーザーが間違えたときに、ただエラーを出すのではなく、正しい使い方を表示するのが親切です。

使い方: myapp <入力ファイル> <出力ファイル>
例: myapp input.txt output.txt

7-2. 数値変換に失敗した場合の処理

数値引数は int.TryParse を使って安全に変換します。

C#
if (args.Length == 0)
{
Console.WriteLine("数値を指定してください。");
return;
}

if (!int.TryParse(args[0], out int number))
{
Console.WriteLine("数値として解釈できません。例: myapp 123");
return;
}

Console.WriteLine($"2倍の値: {number * 2}");

実行例です。

Bash
dotnet run -- abc

出力例です。

数値として解釈できません。例: myapp 123

例外をそのまま表示すると、初心者ユーザーには原因がわかりにくくなります。TryParse を使って、わかりやすいメッセージを出しましょう。

7-3. 存在しないファイルパスが指定された場合の処理

ファイルパスを受け取る場合は、File.Exists で存在確認します。

C#
if (args.Length == 0)
{
Console.WriteLine("読み込むファイルを指定してください。");
return;
}

string path = args[0];

if (!File.Exists(path))
{
Console.WriteLine($"指定されたファイルが存在しません: {path}");
return;
}

string content = File.ReadAllText(path);
Console.WriteLine(content);

さらに実用的にするなら、try-catch で読み込み時の例外にも対応します。

C#
try
{
string content = File.ReadAllText(path);
Console.WriteLine(content);
}
catch (UnauthorizedAccessException)
{
Console.WriteLine("ファイルを読み取る権限がありません。");
}
catch (IOException ex)
{
Console.WriteLine($"ファイルの読み込み中にエラーが発生しました: {ex.Message}");
}

存在確認だけではなく、権限、ロック、文字コードなどで読み込みに失敗する可能性もあります。

7-4. 不正なオプションを検出する方法

手動解析では、知らないオプションを検出する処理を入れましょう。

C#
for (int i = 0; i < args.Length; i++)
{
switch (args[i])
{
case "--name":
i++;
break;

case "--verbose":
break;

default:
Console.WriteLine($"不明なオプションです: {args[i]}");
Console.WriteLine("使い方: myapp --name <名前> [--verbose]");
return;
}
}

不正なオプションを無視すると、ユーザーは指定が効いていると誤解する可能性があります。

Bash
myapp.exe --nmae Taro

このように --name--nmae と打ち間違えた場合、エラーとして知らせるほうが安全です。

7-5. ユーザーにわかりやすいエラーメッセージを書く

良いエラーメッセージには、次の3つが含まれます。

要素
何が問題か--input が指定されていません
どう直すか--input <ファイルパス> を指定してください
具体例例: myapp --input data.txt

悪い例です。

Error

良い例です。

必須オプション --input が指定されていません。
使い方: myapp --input <ファイルパス>
例: myapp --input data.txt

コマンドラインツールは画面がない分、メッセージのわかりやすさが重要です。初心者向け、社内向け、配布向けのどれであっても、エラーメッセージは丁寧に書きましょう。

8. 実用例で学ぶC#コマンドラインアプリの作り方

8-1. 引数で名前を受け取って表示するアプリ

最初の実用例として、名前を受け取って挨拶するアプリを作ります。

C#
if (args.Length == 0)
{
Console.WriteLine("使い方: greet <名前>");
return;
}

string name = args[0];
Console.WriteLine($"こんにちは、{name}さん!");

実行例です。

Bash
dotnet run -- Hanako

出力例です。

こんにちは、Hanakoさん!

オプション形式にするなら、次のようにできます。

C#
string? name = null;

for (int i = 0; i < args.Length; i++)
{
if (args[i] == "--name" && i + 1 < args.Length)
{
name = args[++i];
}
}

if (name == null)
{
Console.WriteLine("使い方: greet --name <名前>");
return;
}

Console.WriteLine($"こんにちは、{name}さん!");

実行例です。

Bash
dotnet run -- --name Hanako

8-2. ファイルを読み込むコマンドラインアプリ

次は、指定されたファイルを読み込んで表示するアプリです。

C#
if (args.Length == 0)
{
Console.WriteLine("使い方: readfile <ファイルパス>");
return;
}

string path = args[0];

if (!File.Exists(path))
{
Console.WriteLine($"ファイルが見つかりません: {path}");
return;
}

foreach (string line in File.ReadLines(path))
{
Console.WriteLine(line);
}

実行例です。

Bash
dotnet run -- sample.txt

ファイルが大きい場合は、File.ReadAllText よりも File.ReadLines のほうが、1行ずつ処理できるため扱いやすい場合があります。

8-3. オプションで処理モードを切り替えるアプリ

--mode オプションで処理を切り替えるサンプルです。

C#
string mode = "normal";

for (int i = 0; i < args.Length; i++)
{
if (args[i] == "--mode" && i + 1 < args.Length)
{
mode = args[++i];
}
}

switch (mode)
{
case "normal":
Console.WriteLine("通常モードで実行します。");
break;

case "fast":
Console.WriteLine("高速モードで実行します。");
break;

case "safe":
Console.WriteLine("安全モードで実行します。");
break;

default:
Console.WriteLine($"不明なモードです: {mode}");
Console.WriteLine("指定できる値: normal, fast, safe");
break;
}

実行例です。

Bash
dotnet run -- --mode fast

出力例です。

高速モードで実行します。

オプションで処理モードを切り替える設計は、ファイル変換、データ集計、ログ出力、テスト実行などでよく使います。

8-4. ログ出力や終了コードを設定する

コマンドラインアプリでは、終了コードも重要です。一般的には、成功時に 0、失敗時に 0 以外を返します。

Main メソッドを int 戻り値にすると、終了コードを返せます。

C#
class Program
{
static int Main(string[] args)
{
if (args.Length == 0)
{
Console.Error.WriteLine("引数が指定されていません。");
return 1;
}

Console.WriteLine("処理に成功しました。");
return 0;
}
}

通常のメッセージは Console.WriteLine、エラーは Console.Error.WriteLine に出すと、標準出力と標準エラー出力を分けられます。

C#
Console.WriteLine("通常ログです。");
Console.Error.WriteLine("エラーログです。");

自動化スクリプトやCI/CDで使うC#コマンドラインツールでは、終了コードが特に重要です。呼び出し元が成功・失敗を判定できるようにしましょう。

8-5. 配布用にビルド・発行する方法

開発中は dotnet run で十分ですが、配布する場合は dotnet publish を使います。

Bash
dotnet publish -c Release

出力先には、実行に必要なファイルが生成されます。

特定のフォルダに出力するなら、次のようにします。

Bash
dotnet publish -c Release -o publish

実行例です。

Bash
publish\CommandLineSample.exe input.txt

環境に依存しない形で配布したい場合は、自己完結型の発行を検討します。

Bash
dotnet publish -c Release -r win-x64 --self-contained true -o publish

ただし、自己完結型にすると配布サイズが大きくなります。社内ツールなら、実行先に.NETランタイムを入れる方式でも十分な場合があります。

9. C#コマンドライン実行でよくあるトラブル

9-1. argsが空になる原因

args が空になる原因として多いのは、引数を渡していないことです。

Bash
dotnet run

この場合、args.Length0 です。

引数を渡すには、次のようにします。

Bash
dotnet run -- apple

Visual Studioでデバッグしている場合は、プロジェクトのデバッグ設定にコマンドライン引数を入力しているか確認しましょう。

また、トップレベルステートメントで args を使っているつもりでも、実行時に引数を渡していなければ空になります。まずは次のコードで確認するのがおすすめです。

C#
Console.WriteLine(args.Length);

9-2. dotnet runで引数が渡らない原因

dotnet run で引数が渡らない場合、-- の位置を確認してください。

誤解しやすい例です。

Bash
dotnet run --configuration Release Taro

この場合、Taro が意図どおりアプリに渡らない可能性があります。

アプリに渡す引数は、次のように -- の後ろに書きます。

Bash
dotnet run --configuration Release -- Taro

dotnet run のオプションと、C#アプリ側の引数を区切るために -- を使う、と覚えておきましょう。

9-3. --の使い方を間違えているケース

-- は、それ自体がC#アプリに渡したい値ではなく、「ここから後ろはアプリの引数」という区切りです。

Bash
dotnet run -- --name Taro

この場合、C#アプリ側の args は次のようになります。

args[0] = --name
args[1] = Taro

-- 自体は通常、args には入りません。

一方で、ビルド済みのexeを直接実行する場合は、dotnet run の区切りとしての -- は不要です。

Bash
myapp.exe --name Taro

開発中の dotnet run と、配布後のexe直接実行では、コマンドの書き方が少し違う点に注意しましょう。

9-4. スペース入り文字列やパスが分割されるケース

スペース入りの文字列やパスが分割される場合は、引用符で囲みます。

悪い例です。

Bash
dotnet run -- C:\My Files\input.txt

分割される可能性があります。

良い例です。

Bash
dotnet run -- "C:\My Files\input.txt"

PowerShell、コマンドプロンプト、Bashでは引用符やエスケープの扱いが異なることがあります。まずは args の中身を表示して、実際にどのように渡されているか確認すると原因を見つけやすいです。

C#
for (int i = 0; i < args.Length; i++)
{
Console.WriteLine($"args[{i}] = {args[i]}");
}

9-5. 文字化けや日本語入力で困ったときの対処法

日本語をコマンドラインで扱う場合、文字化けすることがあります。特にWindowsの古いコンソール環境では、文字コードの影響を受けることがあります。

C#側でUTF-8を指定する例です。

C#
using System.Text;

Console.OutputEncoding = Encoding.UTF8;
Console.InputEncoding = Encoding.UTF8;

Console.WriteLine("日本語の表示テスト");

Windowsのコマンドプロンプトでは、次のコマンドでUTF-8に切り替える方法もあります。

Bash
chcp 65001

ただし、利用しているターミナルやフォントによって表示結果が変わることがあります。Windows Terminal、PowerShell、Visual Studio Codeのターミナルなど、比較的新しい環境を使うと日本語が扱いやすい場合があります。

10. C#コマンドラインに関するよくある質問

10-1. C#でコマンドライン引数はいくつまで渡せる?

C#の string[] args 自体に、実用上すぐ問題になるような小さな固定上限があるわけではありません。ただし、実際にはOSやシェルのコマンドライン長の制限を受けます。

大量のデータを渡したい場合は、引数にすべて詰め込むのではなく、次の方法を検討しましょう。

方法向いているケース
ファイルパスを渡す大量データを処理する
設定ファイルを読む複雑な設定を渡す
標準入力を使うパイプ処理をする
環境変数を使う秘密情報や環境別設定を渡す

たとえば、大量の文字列を引数に並べるより、input.txt を渡してC#側でファイルを読むほうが実用的です。

10-2. Mainメソッドなしでも引数は受け取れる?

はい。トップレベルステートメントを使えば、明示的な Main メソッドを書かなくても args を使えます。

C#
Console.WriteLine($"引数の数: {args.Length}");

foreach (var arg in args)
{
Console.WriteLine(arg);
}

実行例です。

Bash
dotnet run -- hello world

ただし、学習の最初は static void Main(string[] args) の形を理解しておくと、C#のエントリポイントやコマンドライン引数の仕組みがわかりやすくなります。

10-3. コマンドライン引数と標準入力の違いは?

コマンドライン引数は、プログラム起動時に渡す値です。

Bash
myapp.exe input.txt

標準入力は、プログラム実行中に外部から流し込む入力です。

Bash
type input.txt | myapp.exe

C#で標準入力を読む例です。

C#
string? line;

while ((line = Console.ReadLine()) != null)
{
Console.WriteLine($"受け取った行: {line}");
}

使い分けは次のように考えるとよいでしょう。

方式向いている用途
コマンドライン引数ファイル名、モード、オプション指定
標準入力複数行テキスト、パイプ処理、大量データ

たとえば、処理対象のファイル名は引数で渡し、ファイルの中身は標準入力で渡す、という設計もできます。

10-4. オプション解析ライブラリは使うべき?

小さな学習用プログラムなら、まずは args を直接扱う方法で十分です。

C#
string name = args[0];

少し複雑になったら、switch 文で手動解析しても構いません。

C#
switch (args[i])
{
case "--name":
name = args[++i];
break;
}

ただし、次の条件に当てはまるなら、System.CommandLine などのライブラリを検討する価値があります。

条件理由
オプションが多い手動解析が複雑になる
--help が必要自動生成できる
型変換が多いintboolFileInfo などを扱いやすい
サブコマンドがあるadddelete などを整理できる
配布するツール使いやすさと保守性が重要

初心者は、まず手動で仕組みを理解し、その後ライブラリに進むのがおすすめです。

10-5. 初心者はどこまで実装できれば十分?

初心者が最初に目指すゴールは、次の5つです。

目標できること
args を表示できる引数の中身を確認できる
args.Length を使える引数不足を防げる
文字列を受け取れる名前やメッセージを扱える
数値に変換できる計算処理ができる
ファイルパスを扱える実用的なツールを作れる

次のコードが理解できれば、C#コマンドラインの基礎は十分です。

C#
if (args.Length < 2)
{
Console.WriteLine("使い方: myapp <数値1> <数値2>");
return;
}

if (!int.TryParse(args[0], out int a) ||
!int.TryParse(args[1], out int b))
{
Console.WriteLine("数値を指定してください。");
return;
}

Console.WriteLine($"合計: {a + b}");

その次のステップとして、--input--output--verbose のようなオプションを扱えるようになると、実用的なC#コマンドラインアプリを作れるようになります。

まとめ

C#でコマンドライン引数を扱う基本は、string[] args を理解することです。args[0]args[1] のように順番で値を取り出し、args.Length で引数の数を確認すれば、シンプルなコンソールアプリを作れます。

開発中に dotnet run で引数を渡す場合は、次の形を覚えておきましょう。

Bash
dotnet run -- 引数1 引数2

--name--file のようなオプションを扱う場合は、最初は手動解析でも十分です。オプションが増えたり、--help、サブコマンド、型変換、必須チェックが必要になったりしたら、System.CommandLine の導入を検討するとよいでしょう。

C#コマンドラインアプリを作るうえで大切なのは、次のポイントです。

ポイント内容
引数の有無を確認するargs.Length を使う
数値変換は安全に行うTryParse を使う
パスは存在確認するFile.Exists を使う
スペース入り文字列は引用符で囲む"Tokyo Station"
エラー時は使い方を表示するユーザーが直せるメッセージにする
複雑ならライブラリを使うSystem.CommandLine を検討する

まずは、名前を受け取って表示する小さなアプリから始めてみましょう。そこから、数値計算、ファイル読み込み、オプション解析へ進めば、C#で実用的なコマンドラインツールを作れるようになります。