Background and Motivation
This proposal provides a one-liner for the common pattern of starting a process with redirected streams to consume them in the app. Today this looks like:
var psi = new ProcessStartInfo("dotnet", ["build"])
{
RedirectStandardOutput = true,
RedirectStandardError = true,
RedirectStandardInput = true,
};
var process = Process.Start(psi)!;
process.StandardInput.Close(); // prevent child from blocking on stdin
This pattern has:
- Boilerplate: Three boolean properties must be set on
ProcessStartInfo just to opt into redirection.
- Forgetting to close stdin: If the child process reads from stdin and the parent never writes, the child blocks forever. Closing stdin immediately after start is a widespread pattern but easy to forget.
- Null-return:
Process.Start(ProcessStartInfo) returns Process? — the nullable return is a holdover from UseShellExecute scenarios and forces a ! or null-check that is never meaningful when redirecting.
API Proposal
namespace System.Diagnostics
{
public class Process : System.ComponentModel.Component, System.IDisposable
{
public static Process StartRedirected(
ProcessStartInfo startInfo,
bool closeInput = true);
public static Process StartRedirected(
string fileName,
IList<string>? arguments = null,
bool closeInput = true);
}
}
Parameters:
| Parameter |
Description |
startInfo |
A ProcessStartInfo describing the process to start. The Redirect* properties are ignored (redirection is always enabled). UseShellExecute must be false (the default) and StandardInputHandle/StandardOutputHandle/StandardErrorHandle must be null (the default). |
closeInput |
When true (default), standard input is redirected and immediately closed, preventing the child from blocking on stdin. When false, standard input is redirected and left open — the caller can write to process.StandardInput. |
Return value: A non-null Process instance with StandardOutput and StandardError ready for reading (and StandardInput available for writing when closeInput is false).
API Usage
Stream output line-by-line (most common case):
using var process = Process.StartRedirected("dotnet", ["build"]);
try
{
await foreach (var line in process.ReadAllLinesAsync())
{
Console.WriteLine($"[build] {line}");
}
process.WaitForExit();
}
catch
{
process.Kill();
throw;
}
Write input to the child process:
using var process = Process.StartRedirected("sort", closeInput: false);
process.StandardInput.WriteLine("banana");
process.StandardInput.WriteLine("apple");
process.StandardInput.Close(); // signal EOF to child
Use ProcessStartInfo for additional configuration:
var psi = new ProcessStartInfo("dotnet", "test")
{
WorkingDirectory = "/src/myproject",
KillOnParentExit = true,
};
psi.Environment["DOTNET_CLI_TELEMETRY_OPTOUT"] = "1";
using var process = Process.StartRedirected(psi);
Background and Motivation
This proposal provides a one-liner for the common pattern of starting a process with redirected streams to consume them in the app. Today this looks like:
This pattern has:
ProcessStartInfojust to opt into redirection.Process.Start(ProcessStartInfo)returnsProcess?— the nullable return is a holdover fromUseShellExecutescenarios and forces a!or null-check that is never meaningful when redirecting.API Proposal
Parameters:
startInfoProcessStartInfodescribing the process to start. TheRedirect*properties are ignored (redirection is always enabled).UseShellExecutemust befalse(the default) andStandardInputHandle/StandardOutputHandle/StandardErrorHandlemust benull(the default).closeInputtrue(default), standard input is redirected and immediately closed, preventing the child from blocking on stdin. Whenfalse, standard input is redirected and left open — the caller can write toprocess.StandardInput.Return value: A non-null
Processinstance withStandardOutputandStandardErrorready for reading (andStandardInputavailable for writing whencloseInputisfalse).API Usage
Stream output line-by-line (most common case):
Write input to the child process:
Use
ProcessStartInfofor additional configuration: