Processクラスを使うことで子プロセスを起動することができ、起動した子プロセスの標準ストリーム(standard streams)を扱うこともできます。 子プロセスの標準入力(standard input, stdin)や標準出力(standard output, stdout)などの標準ストリームを扱う場合は、ProcessStartInfoクラス標準ストリームをリダイレクトするためのオプションを指定して子プロセスを起動します。

具体的には、RedirectStandardInput/RedirectStandardOutputなどのプロパティをtrueにすることで、子プロセスの標準ストリームをリダイレクトさせます。 これにより、親プロセス側から子プロセスの標準入力へ書き込む/標準出力を読み込むことができます。 .NET Frameworkでの場合、標準ストリームをリダイレクトするにはデフォルトでtrueになっているUseShellExecuteプロパティfalseにする必要があります。

リダイレクトした標準ストリームは、起動した子プロセス(Process)のStandardInput/StandardOutputなどのプロパティを介して操作することができます。 StandardInput/StandardOutputプロパティはStreamWriter/StreamReaderとなっているため、WriteLine/ReadLineといったメソッドにより標準入出力への読み書きを行います。 (§.標準入力への書き込み§.標準出力・標準エラーからの読み込み)

また、標準出力と同様にRedirectStandardError標準エラー(standard error, stderr)をリダイレクトすることにより、StandardErrorプロパティから子プロセスの標準エラーを読み取ることもできます。

ここでは、子プロセスを起動し、標準ストリームを扱う方法について解説します。 子プロセスの起動方法、および起動に際した標準ストリーム以外のオプションについてはプロセス §.子プロセスの起動オプションを、特に子プロセスのコマンドライン引数についてはプロセス §.子プロセスのコマンドライン引数を参照してください。 自プロセスの標準ストリームについては自プロセスの標準入出力を参照してください。

標準入力への書き込み

子プロセスの標準入力への書き込みを行うには、ProcessStartInfoRedirectStandardInputプロパティtrueにした上で子プロセスを起動します。 次に、起動した子プロセスのStandardInputプロパティに設定されるStreamWriterを取得します。 これに対して書き込みを行うことにより、子プロセスの標準入力へ書き込むことができます。

例として、親プロセスparent.exeから子プロセスchild.exeを起動し、子プロセスの標準入力にテキストを書き込む場合は次のようになります。

parent.exe:子プロセスの標準入力へ書き込む
using System;
using System.Diagnostics;

class Sample {
  static void Main()
  {
    // 子プロセスchild.exeの起動オプション
    var psi = new ProcessStartInfo("child.exe") {
      // シェルを使用せず子プロセスを起動する
      // (リダイレクトするために必要)
      // ⚠.NET Core/.NET 5以降ではデフォルトでfalseとなっている
      UseShellExecute = false,

      // 起動した子プロセスの標準入力をリダイレクトする
      RedirectStandardInput = true,
    };

    // 子プロセスを起動する
    using (var child = Process.Start(psi)) {
      // リダイレクトされた子プロセスの標準入力を取得する
      using (var stdin = child.StandardInput) {
        // 標準入力にテキストを書き込む
        stdin.WriteLine("line1");
        stdin.WriteLine("line2");
        stdin.WriteLine("line3");
      } // 標準入力を閉じて書き込みを終了する

      // 子プロセスの終了を待機する
      child.WaitForExit();
    }
  }
}
child.exe:標準入力から読み込んだ内容に行番号を付けて標準出力に書き込む
using System;

class Sample {
  static void Main()
  {
    var lineNumber = 0;

    while (true) {
      // 標準入力から1行読み込む
      var line = Console.ReadLine();

      if (line == null)
        break; // これ以上読み込めない場合は終了

      // 読み込んだ行に行番号を付けて標準出力に書き込む
      Console.WriteLine($"{++lineNumber}: {line}");
    }
  }
}
parent.exe:子プロセスの標準入力へ書き込む
Imports System
Imports System.Diagnostics
Imports System.IO

Class Sample
  Shared Sub Main()
    ' 子プロセスchild.exeの起動オプション
    Dim psi As New ProcessStartInfo("child.exe")
    ' シェルを使用せず子プロセスを起動する
    ' (リダイレクトするために必要)
    ' ⚠.NET Core/.NET 5以降ではデフォルトでFalseとなっている
    psi.UseShellExecute = False
    ' 起動した子プロセスの標準入力をリダイレクトする
    psi.RedirectStandardInput = True

    ' 子プロセスを起動する
    Using child As Process = Process.Start(psi)
      ' リダイレクトされた子プロセスの標準入力を取得する
      Using stdin As StreamWriter = child.StandardInput
        ' 標準入力にテキストを書き込む
        stdin.WriteLine("line1")
        stdin.WriteLine("line2")
        stdin.WriteLine("line3")
      End Using ' 標準入力を閉じて書き込みを終了する

      ' 子プロセスの終了を待機する
      child.WaitForExit()
    End Using
  End Sub
End Class
child.exe:標準入力から読み込んだ内容に行番号を付けて標準出力に書き込む
Imports System

Class Sample
  Shared Sub Main()
    Dim lineNumber As Integer = 0

    Do
      ' 標準入力から1行読み込む
      Dim line As String = Console.ReadLine()

      ' これ以上読み込めない場合は終了
      If line Is Nothing Then Exit Do

      ' 読み込んだ行に行番号を付けて標準出力に書き込む
      lineNumber += 1
      Console.WriteLine($"{lineNumber}: {line}")
    Loop
  End Sub
End Class
実行結果
>parent.exe
1: line1
2: line2
3: line3

このようにして子プロセスの標準入力に書き込みを行うことができます。 子プロセスの標準入力はStreamWriterとして取得できるため、ファイル等への書き込みと同様にStreamWriterのメソッドを使って書き込みを行うことができます。

なお、この例でのchild.exeは標準入力から読み込めなくなるまで読み込みを続けるようになっています。 標準入力の末尾(EOF)に到達するまで読み込み続けるようなプロセスを起動する場合、親プロセスは子プロセスの終了を待機する一方、子プロセスは標準入力が閉じられるまで読み込みを続けようとし、互いに待機状態となったまま処理が進行しないという状況が起こり得ます。 このため親プロセス側では、子プロセスの終了を待機する前にusingステートメントやCloseメソッドの呼び出しによって標準入力を閉じる必要があります。

標準入力を含め標準ストリームをリダイレクトするためにはProcessStartInfo.UseShellExecutefalseである必要があります。 .NET Core/.NET 5以降ではデフォルトでfalseとなっている一方、.NET Frameworkではtrueとなっているため、明示的に指定する必要があります。

子プロセスを起動する際にコマンドライン引数を指定する方法についてはプロセス §.子プロセスのコマンドライン引数を、その他起動時のオプションについてはプロセス §.子プロセスの起動およびプロセス §.子プロセスの起動オプションを参照してください。

標準出力・標準エラーからの読み込み

子プロセスの標準出力からの読み込みを行う場合は、ProcessStartInfoRedirectStandardOutpoutプロパティtrueにして子プロセスを起動します。 次に、起動した子プロセスのStandardOutputプロパティに設定されるStreamReaderを取得します。 これに対して読み込みを行うことにより、子プロセスの標準出力を読み込むことができます。

標準エラーの場合も同様に、RedirectStandardErrorプロパティtrueにし、StandardErrorプロパティのStreamReaderから読み込みます。

例として、親プロセスparent.exeから子プロセスchild.exeを起動し、子プロセスの標準出力・標準エラーからテキストを読み込む場合は次のようになります。

parent.exe:子プロセスの標準出力・標準エラーを読み込む
using System;
using System.Diagnostics;

class Sample {
  static void Main()
  {
    // 子プロセスchild.exeの起動オプション
    var psi = new ProcessStartInfo("child.exe") {
      // シェルを使用せず子プロセスを起動する
      // (リダイレクトするために必要)
      // ⚠.NET Core/.NET 5以降ではデフォルトでfalseとなっている
      UseShellExecute = false,

      // 起動した子プロセスの標準出力・標準エラーをリダイレクトする
      RedirectStandardOutput = true,
      RedirectStandardError = true,
    };

    // 子プロセスを起動する
    using (var child = Process.Start(psi)) {
      string output = null;
      string error = null;

      // リダイレクトされた子プロセスの標準出力を取得する
      using (var stdout = child.StandardOutput)
      using (var stderr = child.StandardError) {
        // 標準出力に書き込まれたテキストをすべて読み込む
        output = stdout.ReadToEnd();
        // 標準エラーに書き込まれたテキストをすべて読み込む
        error = stderr.ReadToEnd();
      } // 標準出力・標準エラーを閉じて読み込みを終了する

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

      // 読み込んだテキストを表示する
      Console.WriteLine($"output: \"{output}\"");
      Console.WriteLine($"error: \"{error}\"");
    }
  }
}
child.exe:標準出力・標準エラーにテキストを書き込む
using System;

class Sample {
  static void Main()
  {
    // 標準出力・標準エラーにテキストを書き込む
    Console.Write("Hello, ");
    Console.WriteLine("stdout!");
    Console.Error.WriteLine("Hello, stderr!");
    Console.WriteLine("Hello, stdout!");
  }
}
parent.exe:子プロセスの標準出力・標準エラーを読み込む
Imports System
Imports System.Diagnostics
Imports System.IO

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
    psi.RedirectStandardError = True

    ' 子プロセスを起動する
    Using child As Process = Process.Start(psi)
      Dim output As String = Nothing
      Dim err As String = Nothing

      ' リダイレクトされた子プロセスの標準出力を取得する
      Using stdout As StreamReader = child.StandardOutput, _
            stderr As StreamReader = child.StandardError
        ' 標準出力に書き込まれたテキストをすべて読み込む
        output = stdout.ReadToEnd()
        ' 標準エラーに書き込まれたテキストをすべて読み込む
        err = stderr.ReadToEnd()
      End Using ' 標準出力・標準エラーを閉じて読み込みを終了する

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

      ' 読み込んだテキストを表示する
      Console.WriteLine($"output: ""{output}""")
      Console.WriteLine($"err: ""{err}""")
    End Using
  End Sub
End Class
child.exe:標準出力・標準エラーにテキストを書き込む
Imports System

Class Sample
  Shared Sub Main()
    ' 標準出力・標準エラーにテキストを書き込む
    Console.Write("Hello, ")
    Console.WriteLine("stdout!")
    Console.Error.WriteLine("Hello, stderr!")
    Console.WriteLine("Hello, stdout!")
  End Sub
End Class
実行結果
>parent.exe
output: "Hello, stdout!
Hello, stdout!
"
err: "Hello, stderr!
"

このようにして子プロセスの標準出力・標準エラーから読み込みを行うことができます。 子プロセスの標準出力・標準エラーはStreamReaderとして取得できるため、ファイル等からの読み込みと同様にStreamReaderのメソッドを使って読み込みを行うことができます。

標準出力・標準エラーを含め標準ストリームをリダイレクトするためにはProcessStartInfo.UseShellExecutefalseである必要があります。 .NET Core/.NET 5以降ではデフォルトでfalseとなっている一方、.NET Frameworkではtrueとなっているため、明示的に指定する必要があります。

子プロセスを起動する際にコマンドライン引数を指定する方法についてはプロセス §.子プロセスのコマンドライン引数を、その他起動時のオプションについてはプロセス §.子プロセスの起動およびプロセス §.子プロセスの起動オプションを参照してください。

標準入出力への読み書き

子プロセスの標準入力のリダイレクト標準出力・標準エラーのリダイレクトは同時に組み合わせることもできます。 これにより、子プロセスの標準入力に書き込み、子プロセスで何らかの処理を行った結果を標準出力から読み込む、さらに子プロセスの標準エラー出力も読み込む、といったことができます。

parent.exe:子プロセスの標準入出力へ読み書きする
using System;
using System.Diagnostics;

class Sample {
  static void Main()
  {
    // 子プロセスchild.exeの起動オプション
    var psi = new ProcessStartInfo("child.exe") {
      // シェルを使用せず子プロセスを起動する
      // (リダイレクトするために必要)
      // ⚠.NET Core/.NET 5以降ではデフォルトでfalseとなっている
      UseShellExecute = false,

      // 起動した子プロセスの標準入出力をリダイレクトする
      RedirectStandardInput = true,
      RedirectStandardOutput = true,
    };

    // 子プロセスを起動する
    using (var child = Process.Start(psi)) {
      string output = null;

      // リダイレクトされた子プロセスの標準入出力を取得する
      using (var stdin = child.StandardInput)
      using (var stdout = child.StandardOutput) {
        // 子プロセスの標準入力に書き込む
        stdin.WriteLine("hello, world!");

        // 子プロセスの標準出力から読み込む
        output = stdout.ReadLine();
      } // 子プロセスの標準入出力を閉じて読み書きを終了する

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

      // 読み込んだテキストを表示する
      Console.WriteLine($"output: \"{output}\"");
    }
  }
}
child.exe:標準入力から読み込み標準出力へ書き込む
using System;

class Sample {
  static void Main()
  {
    // 標準入力から読み込む
    var line = Console.ReadLine();

    // 標準出力へ書き込む
    Console.WriteLine(line.ToUpper()); // 入力を大文字化して出力
  }
}
parent.exe:子プロセスの標準入出力へ読み書きする
Imports System
Imports System.Diagnostics
Imports System.IO

Class Sample
  Shared Sub Main()
    ' 子プロセスchild.exeの起動オプション
    Dim psi As New ProcessStartInfo("child.exe")
    ' シェルを使用せず子プロセスを起動する
    ' (リダイレクトするために必要)
    ' ⚠.NET Core/.NET 5以降ではデフォルトでFalseとなっている
    psi.UseShellExecute = False
    ' 起動した子プロセスの標準入出力をリダイレクトする
    psi.RedirectStandardInput = True
    psi.RedirectStandardOutput = True

    ' 子プロセスを起動する
    Using child As Process = Process.Start(psi)
      Dim output As String = Nothing

      ' リダイレクトされた子プロセスの標準入出力を取得する
      Using stdin As StreamWriter = child.StandardInput, _
            stdout As StreamReader = child.StandardOutput
        ' 子プロセスの標準入力に書き込む
        stdin.WriteLine("hello, world!")

        ' 子プロセスの標準出力から読み込む
        output = stdout.ReadLine()
      End Using ' 子プロセスの標準入出力を閉じて読み書きを終了する

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

      ' 読み込んだテキストを表示する
      Console.WriteLine($"output: ""{output}""")
    End Using
  End Sub
End Class
child.exe:標準入力から読み込み標準出力へ書き込む
Imports System

Class Sample
  Shared Sub Main()
    ' 標準入力から読み込む
    Dim line As String = Console.ReadLine()

    ' 標準出力へ書き込む
    Console.WriteLine(line.ToUpper()) ' 入力を大文字化して出力
  End Sub
End Class
実行結果
>parent.exe
output: "HELLO, WORLD!"

さらに、自プロセスの標準ストリームの入出力先を子プロセスの標準ストリームへリダイレクトすることにより、子プロセスの標準ストリームとパイプすることもできます。

自プロセスと子プロセスの標準ストリームをパイプする

ConsoleクラスSetInSetOutSetErrorの各メソッドを使うことにより、自プロセスの標準ストリームをリダイレクトすることができます。 (自プロセスの標準入出力 §.標準ストリームのリダイレクト) これと子プロセスの標準ストリームのリダイレクトを組み合わせることにより、自プロセスと子プロセスの標準ストリームをパイプ(pipe)することができます。

標準ストリームをパイプするとは、自プロセスの標準出力の出力先を子プロセスの標準入力の入力元とする、また逆に子プロセスの標準出力の出力先を自プロセスの標準入力の入力元とすることで、これによりプロセス間で標準ストリームを介したやりとりをすることができます。

プロセス間での標準ストリームのパイプ
自プロセス 子プロセス
標準出力
Console.Out
標準入力
Console.In
標準入力
Console.In
標準出力
Console.Out
parent.exe:自プロセスと子プロセスの標準入出力をパイプする
using System;
using System.Diagnostics;

class Sample {
  static void Main()
  {
    // 子プロセスchild.exeの起動オプション
    var psi = new ProcessStartInfo("child.exe") {
      // シェルを使用せず子プロセスを起動する
      // (リダイレクトするために必要)
      // ⚠.NET Core/.NET 5以降ではデフォルトでfalseとなっている
      UseShellExecute = false,

      // 起動した子プロセスの標準入出力をリダイレクトする
      RedirectStandardInput = true,
      RedirectStandardOutput = true,
    };

    // 子プロセスを起動する
    using (var child = Process.Start(psi)) {
      string output = null;

      // 初期状態での親プロセスの標準入出力を退避しておく
      var initialIn = Console.In;
      var initialOut = Console.Out;

      // リダイレクトされた子プロセスの標準入出力を取得する
      using (var stdin = child.StandardInput)
      using (var stdout = child.StandardOutput) {
        // 親プロセスの標準出力を子プロセスの標準入力へリダイレクトする
        Console.SetOut(stdin);
        // 親プロセスの標準入力を子プロセスの標準出力へリダイレクトする
        Console.SetIn(stdout);

        // 親プロセスの標準出力へ書き込む
        // →リダイレクトした子プロセスの標準入力に書き込まれる
        Console.WriteLine("hello, world!");

        // 親プロセスの標準入力から読み込む
        // →リダイレクトした子プロセスの標準出力から読み込まれる
        output = Console.ReadLine();

        // 親プロセスの標準入出力を初期状態に元に戻す
        Console.SetIn(initialIn);
        Console.SetOut(initialOut);
      } // 子プロセスの標準入出力を閉じて読み書きを終了する

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

      // 読み込んだテキストを表示する
      // (初期状態に戻した標準出力へ書き込む)
      Console.WriteLine($"output: \"{output}\"");
    }
  }
}
child.exe:標準入力から読み込み標準出力へ書き込む
using System;

class Sample {
  static void Main()
  {
    // 標準入力から読み込む
    // →親プロセスの標準出力に書き込まれた内容が読み込まれる
    var line = Console.ReadLine();

    // 標準出力へ書き込む
    // →親プロセスの標準入力で読み込まれる内容として書き込まれる
    Console.WriteLine(line.ToUpper()); // 入力を大文字化して出力
  }
}
parent.exe:自プロセスと子プロセスの標準入出力をパイプする
Imports System
Imports System.Diagnostics
Imports System.IO

Class Sample
  Shared Sub Main()
    ' 子プロセスchild.exeの起動オプション
    Dim psi As New ProcessStartInfo("child.exe")
    ' シェルを使用せず子プロセスを起動する
    ' (リダイレクトするために必要)
    ' ⚠.NET Core/.NET 5以降ではデフォルトでFalseとなっている
    psi.UseShellExecute = False
    ' 起動した子プロセスの標準入出力をリダイレクトする
    psi.RedirectStandardInput = True
    psi.RedirectStandardOutput = True

    ' 子プロセスを起動する
    Using child As Process = Process.Start(psi)
      Dim output As String = Nothing

      ' 初期状態での親プロセスの標準入出力を退避しておく
      Dim initialIn As TextReader = Console.In
      Dim initialOut As TextWriter = Console.Out

      ' リダイレクトされた子プロセスの標準入出力を取得する
      Using stdin As StreamWriter = child.StandardInput, _
            stdout As StreamReader = child.StandardOutput
        ' 親プロセスの標準出力を子プロセスの標準入力へリダイレクトする
        Console.SetOut(stdin)
        ' 親プロセスの標準入力を子プロセスの標準出力へリダイレクトする
        Console.SetIn(stdout)

        ' 親プロセスの標準出力へ書き込む
        ' →リダイレクトした子プロセスの標準入力に書き込まれる
        Console.WriteLine("hello, world!")

        ' 親プロセスの標準入力から読み込む
        ' →リダイレクトした子プロセスの標準出力から読み込まれる
        output = Console.ReadLine()

        ' 親プロセスの標準入出力を初期状態に元に戻す
        Console.SetIn(initialIn)
        Console.SetOut(initialOut)
      End Using ' 子プロセスの標準入出力を閉じて読み書きを終了する

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

      ' 読み込んだテキストを表示する
      ' (初期状態に戻した標準出力へ書き込む)
      Console.WriteLine($"output: ""{output}""")
    End Using
  End Sub
End Class
child.exe:標準入力から読み込み標準出力へ書き込む
Imports System

Class Sample
  Shared Sub Main()
    ' 標準入力から読み込む
    ' →親プロセスの標準出力に書き込まれた内容が読み込まれる
    Dim line As String = Console.ReadLine()

    ' 標準出力へ書き込む
    ' →親プロセスの標準入力で読み込まれる内容として書き込まれる
    Console.WriteLine(line.ToUpper()) ' 入力を大文字化して出力
  End Sub
End Class
実行結果
>parent.exe
output: "HELLO, WORLD!"

Console.SetIn/SetOut/SetErrorの各メソッドを使って自プロセスの標準ストリームをリダイレクトすると、Consoleクラスでの入出力先はリダイレクト先へと変更されます。 Consoleクラスのデフォルトの入出力ストリームは、プロセスを起動したターミナルやコマンドプロンプト、IDEの出力ウィンドウなどに接続されているため、標準出力・標準エラーをリダイレクトすると以降そこには一切出力されなくなります。

イベントハンドラを使った標準出力の非同期読み込み

Process.BeginOutputReadLine/BeginErrorReadLineメソッドを使うと、標準出力・標準エラーに出力されるたびにイベントを発生させることができ、イベントハンドラによって非同期的に読み込むことができます。

このメソッドを呼び出すと、標準出力・標準エラーに書き込まれた内容はOutputDataReceivedイベント/ErrorDataReceivedイベントとして通知されます。 このイベントは、開始メソッドの名称Begin*ReadLineが示すように、標準出力・標準エラーから1行読み込まれるごとに(改行文字が出力されるたびに)発生します。 また、Processクラスのイベントであるため、ProcessStartInfoクラスで起動オプションと同時にイベントハンドラを設定することはできません。

イベントハンドラとなるDataReceivedEventHandlerデリゲート型では、DataReceivedEventArgs.Dataプロパティを参照することにより、標準出力・標準エラーに出力された内容を得ることができます。 イベントは1行ごとに発生するため、DataReceivedEventArgs.Dataプロパティからは改行文字を除いた1行分の内容が取得されます。

子プロセスが終了して標準出力・標準エラーが閉じられた場合は、Dataプロパティにnull/Nothingが設定された状態でイベントが発生するため、これを元にデータの終端あるいは子プロセスの終了を検知することができます。 なお、イベントハンドラの引数senderには子プロセスのProcessが渡されます。

BeginOutputReadLine/BeginErrorReadLineを使った非同期読み込みは、同期的に読み込む場合と同様にProcessStartInfo.RedirectStandardOutput/ RedirectStandardErrortrueを指定して子プロセスを起動します。 また、子プロセスを起動したあと、読み込みを開始する時点でProcess.BeginOutputReadLine/BeginErrorReadLineメソッドを呼び出します。

一度非同期的な読み込みを開始すると、以降はStandardOutput/StandardErrorを使った同期的な読み込みは例外InvalidOperationExceptionをスローするようになり、一切できなくなります。

parent.exe:子プロセスの標準出力・標準エラーを非同期的に読み込む
using System;
using System.Diagnostics;

class Sample {
  static void Main()
  {
    // 子プロセスを起動するためのProcessを作成
    using (var child = new Process()) {
      // child.exeを子プロセスとして起動する
      child.StartInfo.FileName = "child.exe";

      // シェルを使用せず子プロセスを起動する
      // (リダイレクトするために必要)
      // ⚠.NET Core/.NET 5以降ではデフォルトでfalseとなっている
      child.StartInfo.UseShellExecute = false;

      // 起動した子プロセスの標準出力・標準エラーをリダイレクトする
      child.StartInfo.RedirectStandardOutput = true;
      child.StartInfo.RedirectStandardError = true;

      // リダイレクトした標準出力の内容を受信するイベントハンドラを設定する
      child.OutputDataReceived += (sender, e) => {
        // 子プロセスの標準出力から受信した内容を自プロセスの標準出力に書き込む
        Console.WriteLine($"<stdout> {(e.Data ?? "(end of stream)")}");
      };
      // リダイレクトした標準エラーの内容を受信するイベントハンドラを設定する
      child.ErrorDataReceived += (sender, e) => {
        // 子プロセスの標準エラーから受信した内容を自プロセスの標準エラーに書き込む
        Console.WriteLine($"<stderr> {(e.Data ?? "(end of stream)")}");
      };

      // プロセスを起動する
      child.Start();

      // 標準出力・標準エラーの非同期読み込みを開始する
      child.BeginOutputReadLine();
      child.BeginErrorReadLine();

      // 子プロセスの終了を最大250ミリ秒待機する
      while (!child.WaitForExit(250)) {
        // 終了していない場合は、待機中のメッセージを表示する
        Console.WriteLine("(waiting)");
      }
    }
  }
}
child.exe:一定時間おきに標準出力と標準エラーに出力する
using System;

class Sample {
  static void Main()
  {
    // 100ミリ秒おきに、標準出力と標準エラーに出力する
    for (var i = 0; i < 6; i++) {
      if (i % 2 == 0)
        Console.Error.WriteLine("Hello, stderr!");
      else
        Console.WriteLine("Hello, stdout!");

      System.Threading.Thread.Sleep(100);
    }
  }
}
parent.exe:子プロセスの標準出力・標準エラーを非同期的に読み込む
Option Infer On

Imports System
Imports System.Diagnostics
Imports System.IO

Class Sample
  Shared Sub Main()
    ' 子プロセスを起動するためのProcessを作成
    Using child As New Process
      ' child.exeを子プロセスとして起動する
      child.StartInfo.FileName = "child.exe"

      ' シェルを使用せず子プロセスを起動する
      ' (リダイレクトするために必要)
      ' ⚠.NET Core/.NET 5以降ではデフォルトでFalseとなっている
      child.StartInfo.UseShellExecute = False

      ' 起動した子プロセスの標準出力・標準エラーをリダイレクトする
      child.StartInfo.RedirectStandardOutput = True
      child.StartInfo.RedirectStandardError = True

      ' リダイレクトした標準出力の内容を受信するイベントハンドラを設定する
      AddHandler child.OutputDataReceived, Sub(sender, e)
        ' 子プロセスの標準出力から受信した内容を自プロセスの標準出力に書き込む
        Console.WriteLine($"<stdout> {If(e.Data Is Nothing, "(end of stream)", e.Data)}")
      End Sub
      ' リダイレクトした標準エラーの内容を受信するイベントハンドラを設定する
      AddHandler child.ErrorDataReceived, Sub(sender, e)
        ' 子プロセスの標準出力から受信した内容を自プロセスの標準出力に書き込む
        Console.WriteLine($"<stderr> {If(e.Data Is Nothing, "(end of stream)", e.Data)}")
      End Sub

      ' プロセスを起動する
      child.Start()

      ' 標準出力・標準エラーの非同期読み込みを開始する
      child.BeginOutputReadLine()
      child.BeginErrorReadLine()

      ' 子プロセスの終了を待機する
      Do Until child.WaitForExit(250)
        ' 終了するまでの間、250ミリ秒毎に待機中のメッセージを表示する
        Console.WriteLine("(waiting)")
      Loop
    End Using
  End Sub
End Class
child.exe:一定時間おきに標準出力と標準エラーに出力する
Imports System

Class Sample
  Shared Sub Main()
    ' 100ミリ秒おきに、標準出力と標準エラーに出力する
    For i As Integer = 0 To 5
      If i Mod 2 = 0 Then
        Console.Error.WriteLine("Hello, stderr!")
      Else
        Console.WriteLine("Hello, stdout!")
      End If

      System.Threading.Thread.Sleep(100)
    Next
  End Sub
End Class
実行結果
>parent.exe
<stderr> Hello, stderr!
<stdout> Hello, stdout!
<stderr> Hello, stderr!
(waiting)
<stdout> Hello, stdout!
<stderr> Hello, stderr!
(waiting)
<stdout> Hello, stdout!
<stdout> (end of stream)
<stderr> (end of stream)

一度開始した非同期的に読み込みは、CancelOutputRead/CancelErrorReadメソッドを呼び出すことで中止することができます。 また、中止したあとに再度BeginOutputReadLine/BeginErrorReadLineメソッドを呼び出すことで読み込みを再開することもできます。

CancelOutputRead/CancelErrorReadメソッドを呼び出すとイベントの発生が中止され、再度BeginOutputReadLine/BeginErrorReadLineメソッドを呼び出すとイベントの発生が再開されます。 ただし、中断している間も標準出力・標準エラーの非同期的な読み込み自体は継続して行われているため、再開後は再開以降に標準出力・標準エラーへ書き込まれた分のイベントが発生します。 (再開までの間に書き込まれた分のイベントは発生しない)

parent.exe:子プロセスの標準出力の非同期的読み込みを中断・再開する
using System;
using System.Diagnostics;

class Sample {
  static void Main()
  {
    // 子プロセスを起動するためのProcessを作成
    using (var child = new Process()) {
      // child.exeを子プロセスとして起動する
      child.StartInfo.FileName = "child.exe";

      // シェルを使用せず子プロセスを起動する
      // (リダイレクトするために必要)
      // ⚠.NET Core/.NET 5以降ではデフォルトでfalseとなっている
      child.StartInfo.UseShellExecute = false;

      // 起動した子プロセスの標準出力をリダイレクトする
      child.StartInfo.RedirectStandardOutput = true;

      // リダイレクトした標準出力の内容を受信するイベントハンドラを設定する
      child.OutputDataReceived += (sender, e) => {
        // 子プロセスの標準出力から受信した内容を自プロセスの標準出力に書き込む
        Console.WriteLine($"<stdout> {(e.Data ?? "(end of stream)")}");
      };

      // プロセスを起動する
      child.Start();

      // 標準出力の非同期読み込みを開始する
      child.BeginOutputReadLine();
      Console.WriteLine("(begin)");

      // 500ミリ秒待機する
      System.Threading.Thread.Sleep(500);

      // 標準出力の非同期読み込みを中断する
      child.CancelOutputRead();
      Console.WriteLine("(cancel)");

      // 500ミリ秒待機する (この間も標準出力の非同期読み込みは継続する)
      System.Threading.Thread.Sleep(500);

      // 標準出力の非同期読み込みを再度開始する
      child.BeginOutputReadLine();
      Console.WriteLine("(begin)");

      // 子プロセスの終了を待機する
      child.WaitForExit();
    }
  }
}
child.exe:一定時間おきに標準出力に出力する
using System;

class Sample {
  static void Main()
  {
    // 100ミリ秒おきにカウントアップして標準出力に出力する
    for (var i = 0; i < 15; i++) {
      Console.WriteLine(i);

      System.Threading.Thread.Sleep(100);
    }
  }
}
parent.exe:子プロセスの標準出力の非同期的読み込みを中断・再開する
Option Infer On

Imports System
Imports System.Diagnostics
Imports System.IO

Class Sample
  Shared Sub Main()
    ' 子プロセスを起動するためのProcessを作成
    Using child As New Process
      ' child.exeを子プロセスとして起動する
      child.StartInfo.FileName = "child.exe"

      ' シェルを使用せず子プロセスを起動する
      ' (リダイレクトするために必要)
      ' ⚠.NET Core/.NET 5以降ではデフォルトでFalseとなっている
      child.StartInfo.UseShellExecute = False

      ' 起動した子プロセスの標準出力をリダイレクトする
      child.StartInfo.RedirectStandardOutput = True

      ' リダイレクトした標準出力の内容を受信するイベントハンドラを設定する
      AddHandler child.OutputDataReceived, Sub(sender, e)
        ' 子プロセスの標準出力から受信した内容を自プロセスの標準出力に書き込む
        Console.WriteLine($"<stdout> {If(e.Data Is Nothing, "(end of stream)", e.Data)}")
      End Sub

      ' プロセスを起動する
      child.Start()

      ' 標準出力の非同期読み込みを開始する
      child.BeginOutputReadLine()
      Console.WriteLine("(begin)")

      ' 500ミリ秒待機する
      System.Threading.Thread.Sleep(500)

      ' 標準出力の非同期読み込みを中断する
      child.CancelOutputRead()
      Console.WriteLine("(cancel)")

      ' 500ミリ秒待機する (この間も標準出力の非同期読み込みは継続する)
      System.Threading.Thread.Sleep(500)

      ' 標準出力の非同期読み込みを再度開始する
      child.BeginOutputReadLine()
      Console.WriteLine("(begin)")

      ' 子プロセスの終了を待機する
      child.WaitForExit()
    End Using
  End Sub
End Class
child.exe:一定時間おきに標準出力に出力する
Imports System

Class Sample
  Shared Sub Main()
    ' 100ミリ秒おきにカウントアップして標準出力に出力する
    For i As Integer = 0 To 14
      Console.WriteLine(i)

      System.Threading.Thread.Sleep(100)
    Next
  End Sub
End Class
実行結果
>parent.exe
(begin)
<stdout> 0
<stdout> 1
<stdout> 2
<stdout> 3
<stdout> 4
(cancel)
(begin)
<stdout> 10
<stdout> 11
<stdout> 12
<stdout> 13
<stdout> 14
<stdout> (end of stream)

標準ストリームのエンコーディング

ProcessStartInfoでは、子プロセスの標準ストリームで使用するエンコーディングを指定することができます。 子プロセスの標準ストリームが自プロセスとは異なるエンコーディングを使用しているなど、標準ストリームでのエンコーディングを指定する必要がある場合には、以下のプロパティで指定します。

StandardOutputEncoding/StandardErrorEncodingプロパティでは、標準出力・標準エラーで用いるエンコーディングを指定できます。 .NET Standard 2.1/.NET Core 2.1以降では、StandardInputEncodingプロパティで標準入力のエンコーディングも指定できます。

parent.exe:子プロセスの標準ストリームのエンコーディングを指定して読み書きする .NET Standard 2.1/.NET Core 2.1
using System;
using System.Diagnostics;
using System.Text;

class Sample {
  static void Main()
  {
    // 子プロセスchild.exeの起動オプション
    var psi = new ProcessStartInfo("child.exe") {
      // シェルを使用せず子プロセスを起動する
      // (リダイレクトするために必要)
      // ⚠.NET Core/.NET 5以降ではデフォルトでfalseとなっている
      UseShellExecute = false,

      // 起動した子プロセスの標準入出力をリダイレクトする
      RedirectStandardInput = true,
      RedirectStandardOutput = true,

      // 標準入力への書き込み時にShift_JISを用いる
      StandardInputEncoding = shift_jis,
      // 標準出力からの読み込み時にUTF-8を用いる
      StandardOutputEncoding = Encoding.UTF8,
    };

    // 子プロセスを起動する
    using (var child = Process.Start(psi)) {
      string output = null;

      // リダイレクトされた子プロセスの標準入出力を取得する
      using (var stdin = child.StandardInput)
      using (var stdout = child.StandardOutput) {
        // 子プロセスの標準入力に書き込む
        stdin.WriteLine("こんにちは、世界🌐");

        // 子プロセスの標準出力から読み込む
        output = stdout.ReadLine();
      } // 子プロセスの標準入出力を閉じて読み書きを終了する

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

      // 読み込んだテキストを表示する
      Console.WriteLine($"output: \"{output}\"");
    }
  }

  static readonly Encoding shift_jis =
#if NETFRAMEWORK
    Encoding.GetEncoding("shift_jis");
#else
    // `dotnet add sample.csproj package System.Text.Encoding.CodePages`
    CodePagesEncodingProvider.Instance.GetEncoding("shift_jis");
#endif
}
child.exe:標準入力(Shift_JIS)から読み込み標準出力(UTF-8)に書き込む
using System;
using System.IO;
using System.Security.Cryptography;

class Sample {
  static void Main()
  {
    // 標準入力のエンコーディングにShift_JISを用いる
    Console.InputEncoding = shift_jis;
    // 標準出力のエンコーディングにUTF-8を用いる
    Console.OutputEncoding = Encoding.UTF8;

    // 標準入力から読み込んだ内容をそのまま標準出力に書き込む
    // (Shift_JISからUTF-8へ変換される)
    Console.WriteLine(Console.ReadLine());
  }

  static readonly Encoding shift_jis =
#if NETFRAMEWORK
    Encoding.GetEncoding("shift_jis");
#else
    // `dotnet add sample.csproj package System.Text.Encoding.CodePages`
    CodePagesEncodingProvider.Instance.GetEncoding("shift_jis");
#endif
}
parent.exe:子プロセスの標準ストリームのエンコーディングを指定して読み書きする .NET Standard 2.1/.NET Core 2.1
Imports System
Imports System.Diagnostics
Imports System.IO
Imports System.Text

Class Sample
  Shared Sub Main()
    ' 子プロセスchild.exeの起動オプション
    Dim psi As New ProcessStartInfo("child.exe")
    ' シェルを使用せず子プロセスを起動する
    ' (リダイレクトするために必要)
    ' ⚠.NET Core/.NET 5以降ではデフォルトでFalseとなっている
    psi.UseShellExecute = False
    ' 起動した子プロセスの標準入出力をリダイレクトする
    psi.RedirectStandardInput = True
    psi.RedirectStandardOutput = True

    ' 標準入力への書き込み時にShift_JISを用いる
    psi.StandardInputEncoding = shift_jis
    ' 標準出力からの読み込み時にUTF-8を用いる
    psi.StandardOutputEncoding = Encoding.UTF8

    ' 子プロセスを起動する
    Using child As Process = Process.Start(psi)
      Dim output As String = Nothing

      ' リダイレクトされた子プロセスの標準入出力を取得する
      Using stdin As StreamWriter = child.StandardInput, _
            stdout As StreamReader = child.StandardOutput
        ' 子プロセスの標準入力に書き込む
        stdin.WriteLine("こんにちは、世界🌐")

        ' 子プロセスの標準出力から読み込む
        output = stdout.ReadLine()
      End Using ' 子プロセスの標準入出力を閉じて読み書きを終了する

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

      ' 読み込んだテキストを表示する
      Console.WriteLine($"output: ""{output}""")
    End Using
  End Sub

#If NETFRAMEWORK Then
  Shared ReadOnly shift_jis As Encoding = Encoding.GetEncoding("shift_jis")
#Else
  ' `dotnet add sample.vbproj package System.Text.Encoding.CodePages`
  Shared ReadOnly shift_jis As Encoding = CodePagesEncodingProvider.Instance.GetEncoding("shift_jis")
#End If
End Class
child.exe:標準入力(Shift_JIS)から読み込み標準出力(UTF-8)に書き込む
Imports System
Imports System.IO
Imports System.Security.Cryptography

Class Sample
  Shared Sub Main()
    ' 標準入力のエンコーディングにShift_JISを用いる
    Console.InputEncoding = shift_jis
    ' 標準出力のエンコーディングにUTF-8を用いる
    Console.OutputEncoding = Encoding.UTF8

    ' 標準入力から読み込んだ内容をそのまま標準出力に書き込む
    ' (Shift_JISからUTF-8へ変換される)
    Console.WriteLine(Console.ReadLine())
  End Sub

#If NETFRAMEWORK Then
  Shared ReadOnly shift_jis As Encoding = Encoding.GetEncoding("shift_jis")
#Else
  ' `dotnet add sample.vbproj package System.Text.Encoding.CodePages`
  Shared ReadOnly shift_jis As Encoding = CodePagesEncodingProvider.Instance.GetEncoding("shift_jis")
#End If
End Class
実行結果
>parent.exe
output: "こんにちは、世界??"

StandardInputEncodingプロパティにUTF-8などのエンコーディングを指定すると、子プロセスの標準入力にBOM(Byte Order Mark)が出力される場合があります。 この動作はランタイムの種類や実行環境によって異なります。 詳細についてはEncodingクラスとBOMありなしの制御 §.標準ストリームを参照してください。

標準ストリームでのバイナリデータの読み書き

子プロセスの標準ストリームとして取得できるオブジェクトはStreamWriter/StreamReaderであるため、バイナリ形式での読み書きには向いていません。 また、非同期読み込みでのイベント引数DataReceivedEventArgsも文字列型(≒テキスト形式)として受信されるようになっています。

子プロセスの標準ストリームに対してバイナリ形式の読み書きを行う場合は、まず標準ストリームのStreamWriter/StreamReaderからBaseStreamプロパティを参照することにより、標準ストリームのStreamを取得します。

次に、BaseStreamプロパティから取得したStreamに対して直接読み書きを行うか、StreamからBinaryWriter/BinaryReaderなどを作成して読み書きすることにより、標準ストリームでバイナリ形式のデータを扱うことができるようになります。

parent.exe:子プロセスの標準ストリームからBinaryWriter/BinaryReaderを作成して読み書きする
using System;
using System.Diagnostics;
using System.IO;
using System.Text;

class Sample {
  static void Main()
  {
    // 子プロセスchild.exeの起動オプション
    var psi = new ProcessStartInfo("child.exe") {
      // シェルを使用せず子プロセスを起動する
      // (リダイレクトするために必要)
      // ⚠.NET Core/.NET 5以降ではデフォルトでfalseとなっている
      UseShellExecute = false,

      // 起動した子プロセスの標準入出力をリダイレクトする
      RedirectStandardInput = true,
      RedirectStandardOutput = true,
    };

    // 子プロセスを起動する
    using (var child = Process.Start(psi)) {
      // リダイレクトされた子プロセスの標準入出力を取得する
      var stdin  = child.StandardInput;
      var stdout = child.StandardOutput;

      // BaseStreamを参照して標準入出力のStreamを取得し、
      // BinaryWriter/BinaryReaderを作成する
      using (var stdinWriter = new BinaryWriter(stdin.BaseStream)) {
        // 6バイトのバイナリデータを標準入力へ書き込む
        stdinWriter.Write(new byte[] {0x48, 0x65, 0x6C, 0x6C, 0x6F, 0x21});
      } // 子プロセスの標準入力を閉じて書き込みを終了する

      byte[] output = null;

      using (var stdoutReader = new BinaryReader(stdout.BaseStream)) {
        // 標準出力から最大16バイトのバイナリデータを読み込む
        output = stdoutReader.ReadBytes(16);
      }

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

      // 読み込んだバイナリデータ文字列に変換して表示する
      Console.WriteLine(Encoding.ASCII.GetString(output));
    }
  }
}
child.exe:標準入力の内容をBASE64エンコードして標準出力に書き込む
using System;
using System.IO;
using System.Security.Cryptography;

class Sample {
  static void Main()
  {
    // 標準入力のStreamを取得
    var input = Console.OpenStandardInput();

    // 標準出力のStreamをベースに、書き込まれる内容を
    // BASE64エンコードするCryptoStreamを作成する
    using (var output = new CryptoStream(
      Console.OpenStandardOutput(),
      new ToBase64Transform(),
      CryptoStreamMode.Write,
      leaveOpen: true
    )) {
      // 入力ストリームの内容を出力ストリームに書き込む
      // (BASE64エンコードされた上で書き込まれる)
      input.CopyTo(output);
    }
  }
}
parent.exe:子プロセスの標準ストリームからBinaryWriter/BinaryReaderを作成して読み書きする
Imports System
Imports System.Diagnostics
Imports System.IO
Imports System.Text

Class Sample
  Shared Sub Main()
    ' 子プロセスchild.exeの起動オプション
    Dim psi As New ProcessStartInfo("child.exe")
    ' シェルを使用せず子プロセスを起動する
    ' (リダイレクトするために必要)
    ' ⚠.NET Core/.NET 5以降ではデフォルトでFalseとなっている
    psi.UseShellExecute = False
    ' 起動した子プロセスの標準入出力をリダイレクトする
    psi.RedirectStandardInput = True
    psi.RedirectStandardOutput = True

    ' 子プロセスを起動する
    Using child As Process = Process.Start(psi)
      ' リダイレクトされた子プロセスの標準入出力を取得する
      Dim stdin As StreamWriter = child.StandardInput
      Dim stdout As StreamReader = child.StandardOutput

      ' BaseStreamを参照して標準入出力のStreamを取得し、
      ' BinaryWriter/BinaryReaderを作成する
      Using stdinWriter As New BinaryWriter(stdin.BaseStream)
        ' 6バイトのバイナリデータを標準入力へ書き込む
        stdinWriter.Write(New Byte() {&H48, &H65, &H6C, &H6C, &H6F, &H21})
      End Using ' 子プロセスの標準入力を閉じて書き込みを終了する

      Dim output As Byte()

      Using stdoutReader As New BinaryReader(stdout.BaseStream)
        ' 標準出力から最大16バイトのバイナリデータを読み込む
        output = stdoutReader.ReadBytes(16)
      End Using

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

      ' 読み込んだバイナリデータ文字列に変換して表示する
      Console.WriteLine(Encoding.ASCII.GetString(output))
    End Using
  End Sub
End Class
child.exe:標準入力の内容をBASE64エンコードして標準出力に書き込む
Imports System
Imports System.IO
Imports System.Security.Cryptography

Class Sample
  Shared Sub Main()
    ' 標準入力のStreamを取得
    Dim input As Stream = Console.OpenStandardInput()

    ' 標準出力のStreamをベースに、書き込まれる内容を
    ' BASE64エンコードするCryptoStreamを作成する
    Using output As New CryptoStream(
      Console.OpenStandardOutput(),
      New ToBase64Transform(),
      CryptoStreamMode.Write,
      leaveOpen := True
    )
      ' 入力ストリームの内容を出力ストリームに書き込む
      ' (BASE64エンコードされた上で書き込まれる)
      input.CopyTo(output)
    End Using
  End Sub
End Class
実行結果
>parent.exe
SGVsbG8h

StandardInput/StandardOutput/StandardErrorで取得できるStreamWriter/StreamReaderは内部でバッファリングが行われます。 このため、StandardInput/StandardOutput/StandardErrorを使った読み書きと、BaseStreamから取得したStreamへの読み書きを混在させると入出力内容に不整合が起こる(入出力されるデータの一部の順序が前後する)可能性があり、両者の使用は排他的とする必要があります。

つまり、BaseStreamから取得したStreamで読み書きを行う場合は、それのみを使って読み書きを行う、あるいはどちらか一方をFlushするまではもう一方を使って読み書きしないようにします。

Windows上では、BaseStreamプロパティから取得したStreamを閉じたあとにStandardOutput/StandardErrorを閉じると、StreamWriterがFlushしようとして例外ObjectDisposedExceptionがスローされます。 そのため、StreamWriterとBaseStreamプロパティから取得したStreamの両方をCloseしないように(二重にCloseする動作とならないように)注意する必要があります。

自プロセスの標準ストリームのStreamを取得する方法については自プロセスの標準入出力 §.標準ストリームの取得を参照してください。

Streamクラスを使った読み書きの方法についてはストリームの基本とStreamクラス、BinaryWriterクラス/BinaryReaderクラスを使った読み書きの方法についてはBinaryReaderクラス・BinaryWriterクラスを参照してください。