{"id":112351,"date":"2026-05-25T07:00:00","date_gmt":"2026-05-25T14:00:00","guid":{"rendered":"https:\/\/devblogs.microsoft.com\/oldnewthing\/?p=112351"},"modified":"2026-05-25T21:46:33","modified_gmt":"2026-05-26T04:46:33","slug":"20260525-00","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/oldnewthing\/20260525-00\/?p=112351","title":{"rendered":"A hypothetical redesign of <CODE>System.<WBR>Diagnostics.<WBR>Process<\/CODE> to avoid confusion over properties that are valid only when you are the one who called <CODE>Start<\/CODE>"},"content":{"rendered":"<p>Some time ago, I noted that <a title=\"How can my process read its own standard output?\" href=\"https:\/\/devblogs.microsoft.com\/oldnewthing\/20251205-00\/?p=111843\"> the <code>Process.<wbr \/>Standard\u00adOutput<\/code> property is an attractive nuisance<\/a> because it is valid only on <code>Process<\/code> objects that you called <code>Start<\/code> on. You can&#8217;t just grab any old <code>Process<\/code> object and try to access its standard handles.<\/p>\n<p>Others in the comments had their ideas on how to remove the confusion. Here&#8217;s mine. The principle is that the properties and methods of the <code>Process<\/code> object should be valid for all instances of the <code>Process<\/code> class. If a property or method is valid only conditionally, then either move it to a place that is accessible only if the condition is met, or get rid of it entirely if it adds no value.<\/p>\n<p>The standard handles are the three properties that make sense only for <code>Process<\/code> objects that were created by the static <code>Start<\/code> method. There are also four methods related to those standard handles, as well as two events. Move them all to a new class, call it <code>Process\u00ad<!-- -->Start\u00adResult<\/code>:<\/p>\n<pre>class Process<!-- -->StartResult\r\n{\r\n    public Process Process { get; }\r\n    public System.IO.StreamWriter StandardInput { get; }\r\n    public System.IO.StreamWriter StandardOutput { get; }\r\n    public System.IO.StreamWriter StandardError { get; }\r\n\r\n    public void BeginOutputReadLine();\r\n    public void CancelOutputReadLine();\r\n    public event DataReceivedEventHandler? OutputDataReceived;\r\n\r\n    public void BeginErrorReadLine();\r\n    public void CancelErrorReadLine();\r\n    public event DataReceivedEventHandler? ErrorDataReceived;\r\n}\r\n<\/pre>\n<p>Change the signature of all the overloads of the <code>Start<\/code> method so that they return a <code>Process\u00ad<!-- -->Start\u00adResult<\/code> instead of a <code>Process<\/code>. Now it is impossible to do anything with the standard handles from a process you didn&#8217;t start: If you didn&#8217;t start the process, then you don&#8217;t have a <code>Process\u00ad<!-- -->Start\u00adResult<\/code>. This removes the confusion that existed in the original attempt to have a process read from its own standard output.<\/p>\n<p>This follows <a title=\"How do I design a class so that methods must be called in a certain order?\" href=\"https:\/\/devblogs.microsoft.com\/oldnewthing\/20190318-00\/?p=102324\"> a principle I wrote about earlier<\/a>: To force the developer to do things in a certain order, make the second step dependent on something produced by the first step. In this case, we want to force the developer to call <code>Start<\/code> before they use the standard handles, so we put the members related to the standard handles on a thing that you can obtain only by calling <code>Start<\/code>.<\/p>\n<p>Next, remove the <code>Start\u00adInfo<\/code> property entirely. It serves two purposes:<\/p>\n<ul>\n<li>Prior to calling the <code>Start<\/code> method, it provides a convenient pre-made <code>Process\u00ad<!-- -->Start\u00adInfo<\/code>.<\/li>\n<li>After calling the <code>Start<\/code> method, it holds a copy of the parameters that you passed to the <code>Start<\/code> method.<\/li>\n<\/ul>\n<p>The first purpose is just to cover for people who are too lazy to write the <code>new<\/code> keyword. So don&#8217;t be lazy. Write <code>new Process\u00ad<!-- -->Start\u00adInfo()<\/code>.<\/p>\n<p>The second purpose doesn&#8217;t tell you anything you don&#8217;t already know, since you are the one who passed the parameters to the <code>Start<\/code> method in the first place. If they are so important to you, you can save them yourself.<\/p>\n<p>Removing the <code>Start\u00adInfo<\/code> avoids confusion over whether the properties in it describe the process you want to start, or whether they describe a process that has already started. (And often, it describes neither!)<\/p>\n<p>I think that takes care of the largest source of confusion over the proper use of the <code>Process<\/code> class.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Putting them in a place that can access only if you call <CODE>Start<\/CODE>.<\/p>\n","protected":false},"author":1069,"featured_media":111744,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[1],"tags":[25],"class_list":["post-112351","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-oldnewthing","tag-code"],"acf":[],"blog_post_summary":"<p>Putting them in a place that can access only if you call <CODE>Start<\/CODE>.<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/112351","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/users\/1069"}],"replies":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/comments?post=112351"}],"version-history":[{"count":1,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/112351\/revisions"}],"predecessor-version":[{"id":112352,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/112351\/revisions\/112352"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/media\/111744"}],"wp:attachment":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/media?parent=112351"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/categories?post=112351"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/tags?post=112351"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}