.NETでは、プロセスに関する情報の取得や操作はProcessクラスによって行うことができます。 Processクラスでは、自プロセスや他プロセスを参照し、プロセスの状態・情報を取得することができます。 (§.プロセス情報)

子プロセスを生成する(他のアプリケーションを起動する)場合にもProcessクラスを使います。 (§.子プロセスの起動) 子プロセスを起動する際に指定するコマンドライン引数などのオプションは、ProcessStartInfoで指定することができます。 (§.子プロセスの起動オプション)

ここではProcessクラス、およびプロセスの起動と終了に関する事項としてエントリーポイントコマンドライン引数終了ステータス等についてを解説します。

プロセスの標準入出力については自プロセスの標準入出力、子プロセスを起動して標準入出力への読み書きを行う場合については子プロセスの標準入出力、プロセスの環境変数については環境変数にて個別に解説しています。

自プロセス

ここでは主にコンソールアプリケーションやウィンドウを持つアプリケーションなど、単一のプロセスとして動作するアプリケーションに関して述べます。 スタンドアロンではないサーバーサイドアプリケーション等にはあてはまらない事柄もあるので注意してください。

プロセスのエントリポイント(Mainメソッド)

.NETでは、プロセスのエントリポイントはMainメソッドになります。 このメソッドは任意の名前のクラスに持たせることができます。 プロセス名とクラス名を異なるものにすることもできます。

プロセスのエントリポイント(Mainメソッド)
using System;

class Sample {
  // エントリポイント (Mainメソッド)
  static void Main()
  {
    Console.WriteLine("Hello, world!");
  }
}
プロセスのエントリポイント(非同期のMainメソッド) 
using System;
using System.Threading.Tasks;

class Sample {
  // 非同期のエントリポイント (Mainメソッド)
  static async Task Main()
  {
    await Console.Out.WriteLineAsync("Hello, world!");
  }
}
プロセスのエントリポイント(Mainメソッド)
Imports System

Class Sample
  ' エントリポイント (Mainメソッド)
  Shared Sub Main()
    Console.WriteLine("Hello, world!")
  End Sub
End Class

エントリポイント(Mainメソッド)はstatic(VBではShared)なメソッドにする必要があります。 一方、エントリポイントを持つ型(クラスまたは構造体)自体はstaticである必要はありません。

また、誤って呼び出されてしまう可能性を排除するため、エントリポイントはpublicにするべきではないとされています。 ただし、エントリポイントをpublicにしてもエラー等にはならず、エントリポイントとしての動作に変わりはありません。

Mainという名前のメソッド以外をエントリポイントにすることはできません。 コンパイラはMainという名前のメソッドを探し、それをエントリポイントとして使用します。

プロセスの引数・コマンドライン引数を受け取りたい場合は、Mainメソッドの引数として受け取ります。 コマンドライン引数については§.コマンドライン引数で解説します。

また、Mainメソッドの戻り値をint/Integerにすることで、プロセスの終了ステータスを返すようにすることもできます。 終了ステータスについては§.終了ステータス(終了コード)で解説します。

Mainメソッドを非同期(async)メソッドとする場合、戻り値の型をTaskにします。 終了ステータスを返す場合は、Task<int>にします。 非同期のMainメソッドはC#7.1以降で使用できます。

複数のMainメソッド (エントリポイントの指定)

Mainという名前のメソッドを持つ型が複数ある場合はコンパイルエラーとなります。 この場合、コンパイラオプション/main、またはプロジェクトファイルのプロパティ<StartupObject>によって、どの型のMainメソッドをエントリポイントとするか指定することができます。 /main, <StartupObject>では、Mainメソッドを持つ型を名前空間.型名の形式で指定します。

複数のMainメソッドが存在する場合にエントリポイントとなるメソッドを指定する
using System;

namespace NS {
  class EntryPoint {
    static void Main() // このMainメソッドをエントリポイントとしたい
    {
      Console.WriteLine("Hello, entry point!");
    }
  }
}

class Sample {
  static void Main() // このMainメソッドはエントリポイントとしては使用しない
  {
    Console.WriteLine("Hello, sample!");
  }
}
複数のMainメソッドが存在する場合にエントリポイントとなるメソッドを指定する
Imports System

Namespace NS
  Class EntryPoint
    Shared Sub Main() ' このMainメソッドをエントリポイントとしたい
      Console.WriteLine("Hello, entry point!")
    End Sub
  End Class
End Namespace

Class Sample
  Shared Sub Main() ’ このMainメソッドはエントリポイントとしては使用しない
    Console.WriteLine("Hello, sample!")
  End Sub
End Class
複数のMainメソッドがある場合
test.cs(5,17): error CS0017: プログラムで複数のエントリ ポイントが定義されています。エントリ ポイントを含む型を指定するには、/main でコンパイルしてください。 [sample.csproj]
プロジェクトファイルのStartupObjectでエントリポイントとしたいMainメソッドを持つ型を指定する
<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net5</TargetFramework>
    <StartupObject>NS.EntryPoint</StartupObject> <!-- エントリポイントとしたいMainメソッドを持つ型を指定する -->
  </PropertyGroup>
</Project>
実行結果
$dotnet run
Hello, entry point!
複数のMainメソッドがある場合
>csc /nologo test.cs && test.exe
test.cs(5,17): error CS0017: プログラムで複数のエントリ ポイントが定義されています。エントリ ポイントを含む型を指定する には、/main でコンパイルしてください。
/mainによってエントリポイントとしたいMainメソッドを持つ型を指定する
>csc /nologo /main:NS.EntryPoint test.cs && test.exe
Hello, entry point!
複数のMainメソッドがある場合
$mcs test.cs
test.cs(5,17): error CS0017: Program `test.exe' has more than one entry point defined: `NS.EntryPoint.Main()'
test.cs(13,15): error CS0017: Program `test.exe' has more than one entry point defined: `Sample.Main()'
Compilation failed: 2 error(s), 0 warnings
/mainによってエントリポイントとしたいMainメソッドを持つ型を指定する
$mcs /main:NS.EntryPoint test.cs && mono test.exe
Hello, entry point!

IDE上でもエントリポイントをコンパイルオプションとして変更することができます。 またIDE上では、MainメソッドではなくフォームをWindowsアプリケーションのエントリポイントとして指定する場合もあります。

トップレベルステートメント

C# 9.0以降では、型宣言・Mainメソッド宣言を省略してステートメントを直接記述することができます。 トップレベルステートメントを使ったコードでは、ファイル全体がMainメソッドであるかのように扱われます。

トップレベルステートメントで暗黙のエントリポイントを記述する 
using System;

// 型宣言・メソッド宣言を省略して直接ステートメントを記述できる
string text = "Hello, world!";

Console.WriteLine(text);

#if false // 上記のコードはコンパイル時に以下のように展開される
using System;

namespace RootNamespace {
  class Program {
    static void Main()
    {
      string text = "Hello, world";

      Console.WriteLine(text);
    }
  }
}
#endif
実行結果
Hello, world!

トップレベルステートメントでは、コマンドライン引数をメソッドの引数として取得することができないので、代わりにEnvironment.GetCommandlineArgsメソッドを使って取得します。

構造体でのMainメソッド

通常エントリポイントはクラス(VBではモジュールの場合もある)に持たせることが多く、このようにすることは稀ですが、構造体にエントリポイントをもたせることもできます。

構造体にエントリポイントを持たせる
using System;

// Mainメソッドを持つ型として構造体を用いる
struct Sample {
  static void Main()
  {
    Console.WriteLine("Hello, world!");
  }
}
構造体に非同期のエントリポイントを持たせる 
using System;
using System.Threading.Tasks;

// Mainメソッドを持つ型として構造体を用いる
struct Sample {
  static async Task Main()
  {
    await Console.Out.WriteLineAsync("Hello, world!");
  }
}
構造体にエントリポイントを持たせる
Imports System

' Mainメソッドを持つ型として構造体を用いる
Structure Sample
  Shared Sub Main()
    Console.WriteLine("Hello, world!")
  End Sub
End Structure

コマンドライン引数

プロセスに与えられたコマンドライン引数、起動時に渡される引数は、Mainメソッドの引数として受け取ることができます。 引数の名前は任意に指定できますが、型はString型の配列である必要があります。 Mainメソッドに渡される引数には、プロセスを起動した時のコマンドラインは含まれません

エントリポイント(Mainメソッド)でコマンドライン引数を受け取る
using System;

class Sample {
  // エントリポイントで引数を取る
  // (プロセスに渡された引数はargsを通して参照できる)
  static void Main(string[] args)
  {
    foreach (var arg in args) {
      Console.WriteLine(arg);
    }
  }
}
エントリポイント(Mainメソッド)でコマンドライン引数を受け取る
Imports System

Class Sample
  ' エントリポイントで引数を取る
  ' (プロセスに渡された引数はargsを通して参照できる)
  Shared Sub Main(ByVal args() As String)
    For Each arg As String In args
      Console.WriteLine(arg)
    Next
  End Sub
End Class
実行結果例
>csc test.cs
>test.exe /arg1 /arg2 /foo=bar
/arg1
/arg2
/foo=bar
実行結果
$dotnet build
$dotnet run -- /arg1 /arg2 /foo=bar
/arg1
/arg2
/foo=bar
実行結果
$mcs test.cs
$mono test.exe /arg1 /arg2 /foo=bar
/arg1
/arg2
/foo=bar

プロセスを起動した時のコマンドラインを含めて引数を取得したい場合は、Environment.GetCommandLineArgsメソッドを使います。 詳しくはプロセス・アセンブリの情報 §.コマンドライン引数を参照してください。

コマンドライン引数の解析

コマンドライン引数の解析処理は実装者に任されます。 .NETのクラスライブラリでは、コマンドライン引数の解析を目的としたクラスやメソッド等は用意されていません。 そのため、独自に解析処理を記述するか、サードパーティーのライブラリ等を使用する必要があります。

こういったライブラリの一例として、Mono.Optionsがあります。 Mono.Optionsでは、UnixおよびWindowsでよく用いられる形式のコマンドライン引数の解析をサポートしていて、シンプルなコードで引数の解析を実装することができます。

Mono.Optionsを使ってコマンドライン引数を解析する
using System;
using Mono.Options;

class Sample {
  static void Main(string[] args)
  {
    bool arg1 = false, arg2 = false, arg3 = false;
    string foo = null;

    // コマンドライン引数の定義
    var p = new OptionSet() {
      {"arg1", val => arg1 = (val != null)},
      {"arg2", val => arg2 = (val != null)},
      {"arg3", val => arg3 = (val != null)},
      {"foo=", val => foo = val},
    };

    // 定義に従ってコマンドライン引数を解析する
    p.Parse(args);

    // 解析した結果を表示
    if (arg1)
      Console.WriteLine("arg1が設定されています");
    if (arg2)
      Console.WriteLine("arg2が設定されています");
    if (arg3)
      Console.WriteLine("arg3が設定されています");

    Console.WriteLine("fooの値に'{0}'が指定されています", foo);
  }
}
実行結果例
$mono test.exe /arg1 /arg3 /foo:bar
arg1が設定されています
arg3が設定されています
fooの値に'bar'が指定されています

$mono test.exe --arg2 --foo=hoge
arg2が設定されています
fooの値に'hoge'が指定されています

終了ステータス(終了コード)

.NETでは例外のスローによって異常の発生とその原因を通知することが多いため、終了ステータスを用いることはあまりありませんが、終了ステータスを返すことはできます。

プロセスの終了ステータスを親プロセスに通知するには、Mainメソッドの戻り値をint/Integer、非同期の場合はTask<int>にし、Mainメソッドの戻り値によって終了ステータスを返します。

Mainメソッドの戻り値で終了ステータスを返してプロセスを終了する
using System;
using System.IO;

class Sample {
  static int Main()
  {
    // ファイル'sample.txt'の有無を調べる
    if (File.Exists("sample.txt")) {
      // ファイルがある場合は何らかの処理を行うと仮定する
      return 0; // 正常終了
    }
    else {
      Console.Error.WriteLine("ファイルがありません。");
      return 1; // 異常終了
    }
  }
}
Mainメソッドの戻り値で終了ステータスを返してプロセスを終了する 
using System;
using System.IO;
using System.Threading.Tasks;

class Sample {
  static async Task<int> Main()
  {
    // ファイル'sample.txt'の有無を調べる
    if (File.Exists("sample.txt")) {
      // ファイルがある場合は何らかの処理を行うと仮定する
      // await DoSomethingAsync();
      return 0; // 正常終了
    }
    else {
      await Console.Error.WriteLineAsync("ファイルがありません。");
      return 1; // 異常終了
    }
  }
}
Mainメソッドの戻り値で終了ステータスを返してプロセスを終了する
Imports System
Imports System.IO

Class Sample
  Shared Function Main() As Integer
    ' ファイル'sample.txt'の有無を調べる
    If File.Exists("sample.txt") Then
      ' ファイルがある場合は何らかの処理を行うと仮定する
      Return 0 ' 正常終了
    Else
      Console.Error.WriteLine("ファイルがありません。")
      Return 1 ' 異常終了
    End If
  End Function
End Class

Environment.Exitメソッドを使うことでも終了ステータスの設定を行うことができます。 このメソッドはexitシステムコールに似たもので、終了ステータスを設定すると同時にプロセスを即座に終了します。

Environment.Exitメソッドで終了ステータスの設定と同時にプロセスを終了する
using System;
using System.IO;

class Sample {
  static void Main()
  {
    // ファイル'sample.txt'の有無を調べる
    if (File.Exists("sample.txt")) {
      // ファイルがある場合は何らかの処理を行うと仮定する
      Environment.Exit(0); // 正常終了
    }
    else {
      Console.Error.WriteLine("ファイルがありません。");
      Environment.Exit(1); // 異常終了
    }

    // Environment.Exitが呼び出された時点でプロセスが
    // 終了するため、この部分は実行されない
    Console.WriteLine("Hello, world!");
  }
}
Environment.Exitメソッドで終了ステータスの設定と同時にプロセスを終了する
Imports System
Imports System.IO

Class Sample
  Shared Sub Main()
    ' ファイル'sample.txt'の有無を調べる
    If File.Exists("sample.txt") Then
      ' ファイルがある場合は何らかの処理を行うと仮定する
      Environment.Exit(0) ' 正常終了
    Else
      Console.Error.WriteLine("ファイルがありません。")
      Environment.Exit(1) ' 異常終了
    End If

    ' Environment.Exitが呼び出された時点でプロセスが
    ' 終了するため、この部分は実行されない
    Console.WriteLine("Hello, world!")
  End Sub
End Class

終了ステータスの設定のみを行いたい場合は、Environment.ExitCodeプロパティを設定します。 Environment.Exitメソッドとは異なり、このプロパティに終了ステータスを設定してもプロセスの実行は継続します。

Environment.ExitCodeプロパティで終了ステータスを設定する
using System;
using System.IO;

class Sample {
  static void Main()
  {
    // ファイル'sample.txt'の有無を調べる
    if (File.Exists("sample.txt")) {
      // ファイルがある場合は何らかの処理を行うと仮定する
      Environment.ExitCode = 0; // 正常終了
    }
    else {
      Console.Error.WriteLine("ファイルがありません。");
      Environment.ExitCode = 1; // 異常終了
    }

    // Environment.ExitCodeを設定した後でもプロセスの実行は継続する
    Console.WriteLine("Hello, world!");
  }
}
Environment.ExitCodeプロパティで終了ステータスを設定する
Imports System
Imports System.IO

Class Sample
  Shared Sub Main()
    ' ファイル'sample.txt'の有無を調べる
    If File.Exists("sample.txt") Then
      ' ファイルがある場合は何らかの処理を行うと仮定する
      Environment.ExitCode = 0 ' 正常終了
    Else
      Console.Error.WriteLine("ファイルがありません。")
      Environment.ExitCode = 1 ' 異常終了
    End If

    ' Environment.ExitCodeを設定した後でもプロセスの実行は継続する
    Console.WriteLine("Hello, world!")
  End Sub
End Class

例外発生時の終了ステータス

スローされた例外がハンドリングされないことによりプロセスが終了する場合、終了ステータスとして0以外の値が自動的に設定されます。 例外による終了ステータスはMainメソッドの型がvoidの場合でも設定されます。

ハンドリングされない例外によりプロセスを終了する
using System;
using System.IO;

class Sample {
  static void Main()
  {
    // ファイル'sample.txt'の有無を調べる
    if (File.Exists("sample.txt")) {
      // ファイルがある場合は何らかの処理を行うと仮定する
    }
    else {
      throw new FileNotFoundException("ファイルがありません。", "sample.txt");
    }
  }
}
ハンドリングされない例外によりプロセスを終了する
Imports System
Imports System.IO

Class Sample
  Shared Sub Main()
    ' ファイル'sample.txt'の有無を調べる
    If File.Exists("sample.txt") Then
      ' ファイルがある場合は何らかの処理を行うと仮定する
    Else
      Throw New FileNotFoundException("ファイルがありません。", "sample.txt")
    End If
  End Sub
End Class
実行結果
>dotnet run
Unhandled exception. System.IO.FileNotFoundException: ファイルがありません。
File name: 'sample.txt'
   at Sample.Main()

>echo %ERRORLEVEL%
-532462766
実行結果
>csc /nologo test.cs && test.exe

ハンドルされていない例外: System.IO.FileNotFoundException: ファイルがありません。
   場所 Sample.Main()

>echo %ERRORLEVEL%
-532462766
実行結果
$dotnet run

Unhandled exception. System.IO.FileNotFoundException: ファイルがありません。
File name: 'sample.txt'
   at Sample.Main()

$echo $?
134
実行結果
$mcs test.cs && mono test.exe 

Unhandled Exception:
System.IO.FileNotFoundException: ファイルがありません。
File name: 'sample.txt'
  at Sample.Main () <0x412c2d50 + 0x00067> in <filename unknown>:0 
[ERROR] FATAL UNHANDLED EXCEPTION: System.IO.FileNotFoundException: ファイルがありません。
File name: 'sample.txt'
  at Sample.Main () <0x412c2d50 + 0x00067> in <filename unknown>:0 

$echo $?
1

標準ストリーム (標準入出力)

標準出力への文字列の書き込みにはConsole.WriteLineメソッド、標準入力からの文字列の書き込みにはConsole.ReadLineメソッドなどを使います。 Consoleクラスとプロセスの標準ストリームの扱い方に関しては、自プロセスの標準入出力を参照してください。

プロセス情報

自プロセスに関する情報を取得するには、System.Diagnostics.Processクラスのメンバを参照します。 このクラスでは他プロセスを扱うこともできますが、現在のプロセス(自プロセス)を扱うにはProcess.GetCurrentProcessメソッドによってプロセスのインスタンスを取得します。

Processクラスからは、プロセス名・プロセスIDをはじめとして、以下のような情報を取得することができます。

Processクラスのプロパティ
プロパティ 取得できる情報 備考
基本情報 ProcessName プロセス名
Id プロセスID .NET 5以降では、Environment.ProcessIdプロパティからも自プロセスIDを取得できる
SafeHandle
Handle
プロセスのネイティブハンドル SafeHandleは、.NET Framework 4.6以降で使用可能
BasePriority
PriorityClass
プロセスの優先順位
StartTime プロセスが起動した時刻
プロセッサ使用時間 UserProcessorTime ユーザーモードでのプロセッサ使用時間
PrivilegedProcessorTime 特権モードでのプロセッサ使用時間
TotalProcessorTime ユーザーモード・特権モード合計のプロセッサ使用時間
メモリ PagedMemorySize64 プロセスに割り当てられたメモリ量
WorkingSet64
VirtualMemorySize64ほか
PeakPagedMemorySize64 同ピーク値
PeakWorkingSet64
PeakVirtualMemorySize64ほか
プロパティ 取得できる情報 備考

この他、Processクラスから取得できる情報についてはProcessクラスのドキュメントを参照してください。

Processクラスを使って自プロセスの情報を取得する
using System;
using System.Diagnostics;

class Sample {
  static void Main()
  {
    // 現在のプロセス(プロセス自身)を取得する
    var p = Process.GetCurrentProcess();

    Console.WriteLine(p.ProcessName);         // プロセス名
    Console.WriteLine(p.Id);                  // プロセスID
    Console.WriteLine(Environment.ProcessId); // プロセスID(.NET 5以降)
    Console.WriteLine(p.PriorityClass);       // プロセスの優先順位
    Console.WriteLine(p.SafeHandle);          // プロセスのネイティブハンドル (.NET Framework 4.5以前ではHandleプロパティのみ参照可能)
    Console.WriteLine(p.StartTime);           // プロセスの起動日時
    Console.WriteLine(p.UserProcessorTime);       // ユーザーモードでのプロセッサ使用時間
    Console.WriteLine(p.PrivilegedProcessorTime); // 特権モードでのプロセッサ使用時間
    Console.WriteLine(p.TotalProcessorTime);      // 合計のプロセッサ使用時間
  }
}
Processクラスを使って自プロセスの情報を取得する
Imports System
Imports System.Diagnostics

Class Sample
  Shared Sub Main()
    ' 現在のプロセス(プロセス自身)を取得する
    Dim p As Process = Process.GetCurrentProcess()

    Console.WriteLine(p.ProcessName)          ' プロセス名
    Console.WriteLine(p.Id)                   ' プロセスID
    Console.WriteLine(Environment.ProcessId)  ' プロセスID(.NET 5以降)
    Console.WriteLine(p.PriorityClass)        ' プロセスの優先順位
    Console.WriteLine(p.SafeHandle)           ' プロセスのネイティブハンドル (.NET Framework 4.5以前ではHandleプロパティのみ参照可能)
    Console.WriteLine(p.StartTime)            ' プロセスの起動日時
    Console.WriteLine(p.UserProcessorTime)        ' ユーザーモードでのプロセッサ使用時間
    Console.WriteLine(p.PrivilegedProcessorTime)  ' 特権モードでのプロセッサ使用時間
    Console.WriteLine(p.TotalProcessorTime)       ' 合計のプロセッサ使用時間
  End Sub
End Class

メモリ使用量など、プロセスの詳細な状態を計測するにはパフォーマンスカウンタを用いることもできます。 詳しくはパフォーマンス情報の計測を参照してください。

マシンなどプロセスの実行環境・プラットフォームに関する情報を取得する方法についてはランタイム・システム・プラットフォームの情報、環境変数・自プロセスのパスなどに関する情報についてはプロセス・アセンブリの情報を参照してください。

子プロセス

子プロセスの起動

Process.Startメソッドに実行可能ファイルのパスを指定すると、それを子プロセスとして実行することができます。 .NET Frameworkでは、パスが通っていればコマンドを起動したり、拡張子に関連付けられた実行可能ファイルでファイルを開くこともできます。

Process.Startメソッドを使って子プロセスを起動する
using System;
using System.Diagnostics;

class Sample {
  static void Main(string[] args)
  {
    // 子プロセスchild.exeを起動する
    Process.Start("child.exe");

    // 引数を与えて子プロセスchild.exeを起動する
    Process.Start("child.exe", "/arg1 /arg2 /foo=bar");

    // コマンド"dir"を起動する
    Process.Start("dir"); // ⚠.NET Core/.NET 5以降では、ProcessStartInfoを使用する必要がある

    // ファイル"test.txt"を開く (拡張子.txtに関連付けられた実行可能ファイルで開く)
    Process.Start("test.txt"); // ⚠.NET Core/.NET 5以降では、ProcessStartInfoを使用する必要がある
  }
}
Process.Startメソッドを使って子プロセスを起動する
Imports System
Imports System.Diagnostics

Class Sample
  Shared Sub Main()
    ' 子プロセスchild.exeを起動する
    Process.Start("child.exe")

    ' 引数を与えて子プロセスchild.exeを起動する
    Process.Start("child.exe", "/arg1 /arg2 /foo=bar")

    ' コマンド"dir"を起動する
    Process.Start("dir") ' ⚠.NET Core/.NET 5以降では、ProcessStartInfoを使用する必要がある

    ' ファイル"test.txt"を開く (拡張子.txtに関連付けられた実行可能ファイルで開く)
    Process.Start("test.txt") ' ⚠.NET Core/.NET 5以降では、ProcessStartInfoを使用する必要がある
  End Sub
End Class

ProcessStartInfoを使用することで、子プロセスを起動する際のオプションを詳細に指定することができます。

子プロセスの終了の待機・終了ステータスの取得

Process.Startメソッドでプロセスを起動した場合、起動した時点で処理が戻ります。 起動したプロセスの終了を待機する場合は、Process.WaitForExitメソッドまたはProcess.WaitForExitAsyncメソッドを使います。

Process.Startメソッドは起動したプロセスをProcessインスタンスとして返すので、このインスタンスに対してWaitForExitメソッドを呼び出すことにより、起動したプロセスの終了を待機することができます。 また、ExitCodeプロパティを参照すれば終了ステータスを取得することもできます。

Process.WaitForExitメソッドを使って起動した子プロセスの終了を待機する
using System;
using System.Diagnostics;

class Sample {
  static void Main()
  {
    // 子プロセスchild.exeを起動する
    using (var p = Process.Start("child.exe")) {
      // 起動した子プロセスの終了を待機する
      p.WaitForExit();

      Console.WriteLine("プロセスが終了しました。 終了ステータス: {0}", p.ExitCode);
    }
  }
}
Process.WaitForExitAsyncメソッドを使って起動した子プロセスの終了を待機する 
using System;
using System.Diagnostics;
using System.Threading.Tasks;

class Sample {
  static async Task Main()
  {
    // 子プロセスchild.exeを起動する
    using (var p = Process.Start("child.exe")) {
      // 起動した子プロセスの終了を待機する
      await p.WaitForExitAsync();

      Console.WriteLine("プロセスが終了しました。 終了ステータス: {0}", p.ExitCode);
    }
  }
}
Process.WaitForExitメソッドを使って起動した子プロセスの終了を待機する
Imports System
Imports System.Diagnostics

Class Sample
  Shared Sub Main()
    ' 子プロセスchild.exeを起動する
    Using p As Process = Process.Start("child.exe")
      ' 起動した子プロセスの終了を待機する
      p.WaitForExit()

      Console.WriteLine("プロセスが終了しました。 終了ステータス: {0}", p.ExitCode)
    End Using
  End Sub
End Class

WaitForExitメソッドではタイムアウト時間をミリ秒単位で指定することもできます。 タイムアウトまでにプロセスが終了したかどうかは戻り値によって知ることができます。

Process.WaitForExitメソッドでタイムアウト時間を指定して子プロセスの終了を待機する
using System;
using System.Diagnostics;

class Sample {
  static void Main()
  {
    // 子プロセスchild.exeを起動する
    using (var p = Process.Start("child.exe")) {
      for (;;) {
        // 起動した子プロセスの終了を待機する
        // (1秒=1000ミリ秒でタイムアウトする)
        if (p.WaitForExit(1000)) {
          Console.WriteLine("プロセスが終了しました");
          // 待機を終了する
          break;
        }
        else {
          // 待機がタイムアウトした
          Console.WriteLine("プロセスは処理中です");
          // 待機を継続する
        }
      }
    }
  }
}
Process.WaitForExitメソッドでタイムアウト時間を指定して子プロセスの終了を待機する
Imports System
Imports System.Diagnostics

Class Sample
  Shared Sub Main()
    ' 子プロセスchild.exeを起動する
    Using p As Process = Process.Start("child.exe")
      Do
        ' 起動した子プロセスの終了を待機する
        ' (1秒=1000ミリ秒でタイムアウトする)
        If p.WaitForExit(1000) Then
          Console.WriteLine("プロセスが終了しました")
          ' 待機を終了する
          Exit Do
        Else
          ' 待機がタイムアウトした
          Console.WriteLine("プロセスは処理中です")
          ' 待機を継続する
        End If
      Loop
    End Using
  End Sub
End Class

起動したプロセスを即座に終了させたい場合は、Process.Killメソッドを使うことができます。

子プロセスの起動オプション

ProcessStartInfoクラスを用いると、引数以外にも子プロセス起動時のオプションや動作を細かく指定することができます。 環境変数やカレントディレクトリを変更した状態で起動したい場合には、ProcessStartInfoクラスを使って子プロセスを起動する必要があります。

例として、コマンドなどを子プロセスとして起動したい場合・シェルを使用して起動したい場合には、UseShellExecutetrueにします。 逆に、実行可能ファイルを直接指定して子プロセスとして起動したい場合や、標準入出力を扱いたい場合環境変数を設定して起動する場合は、falseにする必要があります。 UseShellExecuteはランタイムによってデフォルト値が異なり、.NET Frameworkではtrue、.NET Core/.NET 5ではfalseとなっています。

また、Windows上でコマンドラインプロセス(コンソールアプリケーション)を起動する場合にDOSプロンプトウィンドウを表示したくない場合には、CreateNoWindowtrueを指定して起動します。 CreateNoWindowはデフォルトではfalseとなっています。

ProcessStartInfoクラスでオプションを指定して子プロセスを起動する
using System;
using System.Diagnostics;

class Sample {
  static void Main()
  {
    // 子プロセスchild.exeの起動パラメータを作成する
    var psi = new ProcessStartInfo("child.exe");

    // 子プロセスに渡すコマンドライン引数を設定する
    psi.Arguments = "/arg1 /arg2 /foo=bar";

    // .NET Standard 2.1/.NET Core 2.1以降では、ArgumentListプロパティを使用することもできる
    // (ArgumentListプロパティでは、空白を含むパスなど、必要なエスケープが自動的に行われる)
    psi.ArgumentList.Add("/arg1");
    psi.ArgumentList.Add("/arg2");
    psi.ArgumentList.Add("/foo=bar");

    // プロセス起動時の作業ディレクトリ(カレントディレクトリ)を設定する
    psi.WorkingDirectory = @".\temp\";

    // 環境変数を設定する
    psi.EnvironmentVariables["PATH_DATA"] = @".\data\";

    // プロセス起動時にウィンドウを生成しない
    // (Windows上でコマンドラインプロセスを起動する場合に
    // DOSプロンプトウィンドウを表示せずに起動する)
    psi.CreateNoWindow = true;

    // シェルを使用せずに子プロセスを起動する
    // (環境変数を設定したり、標準ストリームをリダイレクトする場合など、
    // 一部オプションを使用する際にはfalseとする必要がある)
    psi.UseShellExecute = false;

    // 上記のパラメータで子プロセスを起動する
    using (var child = Process.Start(psi)) {
      // 子プロセスの終了を待機する
      child.WaitForExit();
    }
  }
}
ProcessStartInfoクラスでオプションを指定して子プロセスを起動する
Imports System
Imports System.Diagnostics

Class Sample
  Shared Sub Main()
    ' 子プロセスchild.exeの起動パラメータを作成する
    Dim psi As New ProcessStartInfo("child.exe")

    ' 子プロセスに渡すコマンドライン引数を設定する
    psi.Arguments = "/arg1 /arg2 /foo=bar"

    ' .NET Standard 2.1/.NET Core 2.1以降では、ArgumentListプロパティを使用することもできる
    ' (ArgumentListプロパティでは、空白を含むパスなど、必要なエスケープが自動的に行われる)
    psi.ArgumentList.Add("/arg1")
    psi.ArgumentList.Add("/arg2")
    psi.ArgumentList.Add("/foo=bar")

    ' プロセス起動時の作業ディレクトリ(カレントディレクトリ)を設定する
    psi.WorkingDirectory = ".\temp\"

    ' 環境変数を設定する
    psi.EnvironmentVariables("PATH_DATA") = ".\data\"

    ' プロセス起動時にウィンドウを生成しない
    ' (Windows上でコマンドラインプロセスを起動する場合に
    ' DOSプロンプトを表示せずに起動する)
    psi.CreateNoWindow = True

    ' シェルを使用せずに子プロセスを起動する
    ' (環境変数を設定したり、標準ストリームをリダイレクトする場合など、
    ' 一部オプションを使用する際にはFalseとする必要がある)
    psi.UseShellExecute = False

    ' 上記のパラメータで子プロセスを起動する
    Using child As Process = Process.Start(psi)
      ' 子プロセスの終了を待機する
      child.WaitForExit()
    End Using
  End Sub
End Class

子プロセスの起動と環境変数の設定については環境変数 §.子プロセスの環境変数でも解説しています。

子プロセスのコマンドライン引数

子プロセスを起動する際のコマンドライン引数は、ProcessStartInfoクラスのArgumentsプロパティで指定することができます。 ただし、このプロパティは引数すべてを単一の文字列として指定する必要があり、また空白 を含む文字列とダブルクオーテーション"のエスケープが必要な場合はエスケープ済みの文字列を指定する必要があります。

.NET Standard 2.1/.NET Core 2.1以降では、ArgumentListプロパティを使用することもできます。 このプロパティは引数を文字列のコレクションとして指定することができ、また空白 を含む文字列とダブルクオーテーション"のエスケープも自動的に行われます。

コマンドライン引数はArgumentsまたはArgumentListのどちらか一方のみが設定でき、両方設定した状態でProcess.Startメソッドを呼び出すとInvalidOperationExceptionをスローします。 また、Argumentsに設定した値はArgumentListには反映されず、逆もまた同様です。

実行結果
/esc="
/dir1=C:\Program
Files\dotnet
/dir2=C:\Program Files\dotnet
/dir3 = C:\Program Files\dotnet
実行結果
/esc="
/dir1=C:\Program Files\dotnet
/dir2="C:\Program Files\dotnet"
"/dir3 = C:\Program Files\dotnet"
ProcessStartInfoクラスで子プロセスのコマンドライン引数を指定する
using System;
using System.Diagnostics;

class Sample {
  static void Main(string[] args)
  {
    // 子プロセスとして起動された場合は、コマンドライン引数を一行ごとに表示して終了する
    if (!string.IsNullOrEmpty(Environment.GetEnvironmentVariable("CHILD"))) {
      foreach (var arg in args) {
        Console.WriteLine(arg);
      }
      return;
    }

    // 自プロセスと同じ実行可能ファイルに、コマンドライン引数を与えて子プロセスとして起動する
    var psi = new ProcessStartInfo(Process.GetCurrentProcess().MainModule.FileName) {
#if true
      // Argumentsプロパティでコマンドライン引数を指定する
      Arguments = @"/esc=\"" /dir1=C:\Program Files\dotnet /dir2=""C:\Program Files\dotnet"" ""/dir3 = C:\Program Files\dotnet""",
#else
      // ArgumentListプロパティでコマンドライン引数を指定する
      ArgumentList = {@"/esc=""", @"/dir1=C:\Program Files\dotnet", @"/dir2=""C:\Program Files\dotnet""", @"""/dir3 = C:\Program Files\dotnet"""},
#endif
      EnvironmentVariables = { {"CHILD", "1"} }, // 子プロセスとして起動したことを示す環境変数を設定しておく (再帰的なプロセス生成を抑止する)
      UseShellExecute = false, // ⚠.NET Core/.NET 5以降ではデフォルトでfalseとなっているため、明示的に指定しなくても可
    };

    // 上記のパラメータで子プロセスを起動する
    using (var child = Process.Start(psi)) {
      // 子プロセスの終了を待機する
      child.WaitForExit();
    }
  }
}
ProcessStartInfoクラスで子プロセスのコマンドライン引数を指定する
Imports System
Imports System.Diagnostics

Class Sample
  Shared Sub Main(ByVal args() As String)
    ' 子プロセスとして起動された場合は、コマンドライン引数を一行ごとに表示して終了する
    If Not String.IsNullOrEmpty(Environment.GetEnvironmentVariable("CHILD")) Then
      For Each arg As String In args
        Console.WriteLine(arg)
      Next
      Return
    End If

    ' 自プロセスと同じ実行可能ファイルに、コマンドライン引数を与えて子プロセスとして起動する
    Dim psi As New ProcessStartInfo(Process.GetCurrentProcess().MainModule.FileName)

#If True
    ' Argumentsプロパティでコマンドライン引数を指定する
    psi.Arguments = "/esc=\"" /dir1=C:\Program Files\dotnet /dir2=""C:\Program Files\dotnet"" ""/dir3 = C:\Program Files\dotnet"""
#Else
    ' ArgumentListプロパティでコマンドライン引数を指定する
    psi.ArgumentList.Add("/esc=""")
    psi.ArgumentList.Add("/dir1=C:\Program Files\dotnet")
    psi.ArgumentList.Add("/dir2=""C:\Program Files\dotnet""")
    psi.ArgumentList.Add("""/dir3 = C:\Program Files\dotnet""")
#End If

    psi.EnvironmentVariables("CHILD") = "1" ' 子プロセスとして起動したことを示す環境変数を設定しておく (再帰的なプロセス生成を抑止する)
    psi.UseShellExecute = False ' ⚠.NET Core/.NET 5以降ではデフォルトでFalseとなっているため、明示的に指定しなくても可


    ' 上記のパラメータで子プロセスを起動する
    Using child As Process = Process.Start(psi)
      ' 子プロセスの終了を待機する
      child.WaitForExit()
    End Using
  End Sub
End Class

上記のコードでは、コマンドライン引数を与えた上で再帰的にプロセスを起動しています。 上記のコードで環境変数CHILDを指定せずに実行した場合Fork爆弾と同様の動作となるため、コードを改変する場合は注意してください。

子プロセスの標準ストリーム(標準入出力)

子プロセスの標準ストリームをリダイレクトしたい(子プロセスの標準出力を読み取る・標準入力に書き込む)場合は、ProcessStartInfo.RedirectStandardOutputtrueにし、同時にProcessStartInfo.UseShellExecutefalseにして子プロセスを起動します。

標準出力に書き込まれた内容を読み込むには、Process.StandardOutput.ReadToEndメソッドなどによって取得します。

子プロセスの標準出力をリダイレクトして標準出力に書き込まれた内容を読み込む
using System;
using System.Diagnostics;

class Sample {
  static void Main()
  {
    // 子プロセスchild.exeの起動パラメータを作成する
    var psi = new ProcessStartInfo("child.exe");

    // シェルを使用せず子プロセスを起動する
    // ⚠標準入出力をリダイレクトするために必要
    // ⚠.NET Core/.NET 5以降ではデフォルトでfalseとなっているため、明示的に指定しなくても可
    psi.UseShellExecute = false;

    // 子プロセスの標準出力をリダイレクトする
    psi.RedirectStandardOutput = true;

    // 子プロセスを起動する
    using (var child = Process.Start(psi)) {
      // リダイレクトした子プロセスの標準出力からテキストを読み込む
      var stdout = child.StandardOutput.ReadToEnd();

      // 子プロセスの終了を待機する
      child.WaitForExit();

      // 読み込んだテキストを表示する
      Console.WriteLine(stdout);
    }
  }
}
子プロセスの標準出力をリダイレクトして標準出力に書き込まれた内容を読み込む
Imports System
Imports System.Diagnostics

Class Sample
  Shared Sub Main()
    ' 子プロセスchild.exeの起動パラメータを作成する
    Dim psi As New ProcessStartInfo("child.exe")

    ' シェルを使用せず子プロセスを起動する
    ' ⚠標準入出力をリダイレクトするために必要
    ' ⚠.NET Core/.NET 5以降ではデフォルトでfalseとなっているため、明示的に指定しなくても可
    psi.UseShellExecute = False

    ' 子プロセスの標準出力をリダイレクトする
    psi.RedirectStandardOutput = True

    ' 子プロセスを起動する
    Using child As Process = Process.Start(psi)
      ' リダイレクトした子プロセスの標準出力からテキストを読み込む
      Dim stdout As String = child.StandardOutput.ReadToEnd()

      ' 子プロセスの終了を待機する
      child.WaitForExit()

      ' 読み込んだテキストを表示する
      Console.WriteLine(stdout)
    End Using
  End Sub
End Class

子プロセスの標準ストリームはProcess.StandardInputProcess.StandardOutputProcess.StandardErrorプロパティよりStreamReaderクラス/StreamWriterクラスとして取得することができます。 StreamReader/StreamWriterを使った読み書きについてはStreamReaderクラス・StreamWriterクラスを参照してください。

このほか、子プロセスの標準ストリームの扱い方や、非同期での標準ストリームへの読み書きなど、より具体的な方法は子プロセスの標準入出力で解説しています。

他プロセスの取得

Process.GetProcessesメソッドを使うと、現在起動中の取得可能なプロセスを取得することができます。

また、Process.GetProcessByIdメソッドProcess.GetProcessesByNameメソッドを使うと、プロセスIDまたはプロセス名から該当するプロセスの情報を取得することができます。

プロセス間の協調

排他制御

プロセス間で排他制御を行うには、ミューテックス(System.Threading.Mutexクラス)やセマフォ(System.Threading.Semaphoreクラス)を用いることができます。

これらのクラスの詳細や、これらを使ったプロセス間排他制御の具体例については、以下のページを参照してください。

プロセス間通信

ここで紹介する手法は.NET Frameworkのみで使用できます。 .NET Core/.NET 5以降では使用できません。

System.Runtime.Remoting名前空間にあるクラスを使うことでプロセス間通信を行うことができます。 ここではその一例として、IPCチャンネルを使ったプロセス間通信を例示します。

まず、サーバー・クライアントの両方で使用される、通信オブジェクトとなるクラスを用意します。 このクラスは、MarshalByRefObjectを継承する必要があります。 MarshalByRefObjectは、リモート処理によってプロセス境界(厳密にはアプリケーションドメイン境界)を超えるオブジェクトに必要な機能を備えた基本クラスです。

リモートオブジェクト(通信オブジェクト)となるクラス
using System;

// リモートオブジェクトとして公開するクラス
public class Message : MarshalByRefObject {
  public string Text {
    get; set;
  }
}

次にサーバー側です。 サーバー側では、IpcServerChannelクラスでチャネルを登録し、RemotingServices.Marshalメソッドで上記のリモートオブジェクトを公開します。

IpcServerChannelクラスを使用する場合、System.Runtime.Remoting.dllをアセンブリ参照に追加する必要があります。

サーバー側プロセスのコード
using System;
using System.Runtime.Remoting;
using System.Runtime.Remoting.Channels;
using System.Runtime.Remoting.Channels.Ipc;

// サーバー側となるクラス
class Server {
  private readonly Message message = new Message();

  public Server()
  {
    // ポート名"ipc-test"でチャンネルを登録
    var channel = new IpcServerChannel("ipc-test");

    ChannelServices.RegisterChannel(channel, false);

    // 登録したチャンネルでリモートオブジェクト"message"を公開する
    message = new Message();
    message.Text = "Hello, world";

    RemotingServices.Marshal(message, "message", typeof(Message));
  }

  static void Main()
  {
    // サーバーを起動して待機する
    var s = new Server();

    for (;;) {
      Console.ReadLine();
    }
  }
}

最後にクライアント側です。 クライアント側では、サーバー同様にIpcClientChannelクラスでチャネルを登録し、Activator.GetObjectメソッドで公開されているリモートオブジェクトを取得します。

IpcClientChannelクラスを使用する場合、System.Runtime.Remoting.dllをアセンブリ参照に追加する必要があります。

クライアント側プロセスのコード
using System;
using System.Runtime.Remoting;
using System.Runtime.Remoting.Channels;
using System.Runtime.Remoting.Channels.Ipc;

// クライアント側となるクラス
class Client {
  private readonly Message message;

  public Message Message {
    get { return message; }
  }

  public Client()
  {
    // クライアント側のチャンネルを登録
    var channel = new IpcClientChannel();

    ChannelServices.RegisterChannel(channel, false);

    // ポート名"ipc-test"のリモートオブジェクト"message"を取得する
    message = Activator.GetObject(typeof(Message), "ipc://ipc-test/message") as Message;
  }

  static void Main()
  {
    // クライアントを起動して取得したリモートオブジェクトを参照する
    var c = new Client();

    Console.WriteLine(c.Message.Text);
  }
}

サーバー側の起動後にクライアント側を起動すると、サーバー側で設定された文字列を取得することができます。 ICPのほか、System.Runtime.Remoting名前空間のクラスを使うことでHTTP・TCPを用いたプロセス間通信を行うこともできます。