Skip to content

[API Proposal]: Process.StartRedirected #128473

@tmds

Description

@tmds

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:

  1. Boilerplate: Three boolean properties must be set on ProcessStartInfo just to opt into redirection.
  2. 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.
  3. 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);

Metadata

Metadata

Assignees

No one assigned

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions