System.Shell.CommandLine does not belong in System.Core

Update: Justin Van Patten at Microsoft clarifies that the System.Shell.CommandLine API that was on the CTP for VS2010 will not be part of the final .NET 4.0. Instead better versions (similar in spirit to Mono.Options) will be made available in CodePlex in the future. Relief.

Update 2: Justin gave me permission to quote from his private email, which I include:

We are not shipping System.Shell.CommandLine in .NET 4. This was based on an intern project from a couple of years back that was mistakenly public in the .NET Framework 4.0 CTP. It wasn't a design that we were happy with and has been removed and will not be present in the next preview release.

We have a *much better* command line parsing API, along the lines of Mono.Options, that we're planning to release on CodePlex later this year.

Today I was alarmed by a new API being introduced into .NET 4.0, the System.Shell.CommandLine which is being dumped into System.Core.

An introductory blog post shows a bloated, over-engineered, too rich in the OO, too poor in the taste look at the API. Not only it is a terrible API, it is being dumped right in the core of the framework on the System.Core assembly.

This is the kind of API that you get when the work is commissioned as opposed to be created as an act of love. This is what you get from a culture of process. Some PM figured out "We need command line parsing". And since it does not look like rocket science they assigned this to someone that had absolutely no interest in this task. And clearly nobody involved (the PM, the developer and the tester) fought back. They were forced to do this, and they came up with this aberration, hoping that the sooner they were done with this, the sooner they could move on with their lives.

Compare this with the labor of love from Jonathan Pryor and his Mono.Options library. This API was discussed publicly, it was adopted in a few applications and tried out, it was morphed to support Windows, Unix and GNU style command line options and the result shows what passion and love deliver when it comes to APIs.

Compare and contrast. This is the sample posted for on the blog above:

	using System;
	using System.Shell.CommandLine;

	class Program
	{
	static void Main()
	        {
	            Console.WriteLine("Checks a disk and displays a status report.\n");

	            CommandLineParser cmd = new CommandLineParser();
	            cmd.AddParameter(CommandLineParameterType.String, "volume", ParameterRequirement.Required,
	            ParameterNameRequirement.NotRequired, "Specifies the drive letter.");
	            cmd.AddParameter(CommandLineParameterType.Boolean, "F",      ParameterRequirement.NotRequired,
	            ParameterNameRequirement.Required,    "Fixes errors on the disk.");
	            cmd.AddParameter(CommandLineParameterType.Int32, "L",      ParameterRequirement.NotRequired,
	            ParameterNameRequirement.Required,    "Changes the log file size to the specified number of kilobytes.");

	            try
	            {
	                cmd.Parse();
	            }
	            catch (ParameterParsingException ex)
	            {
	                Console.WriteLine(cmd.GetHelp());
	                Console.WriteLine(ex.Message);
	                return;
	            }

	            string volume = cmd.GetStringParameterValue ("volume");
	            bool fix      = cmd.GetBooleanParameterValue("F");
	            int? logSize  = cmd.GetInt32ParameterValue  ("L");

	            Console.WriteLine("Checking volume {0}...", volume);

	            if (fix)
	                Console.WriteLine("Fixing errors...");

	            if (logSize != null)
	                Console.WriteLine("Changing log size to {0}...", logSize);
	            else
	                Console.WriteLine("Current log size: {0}", 1024);
	        }
	    }
	

This is the equivalent in Mono.Options. With Mono.Options you can take advantage of C# 3 features and make the code more succinct. An important style difference is that the policy is not limited by what can be poorly expressed by an enumeration but can be anything that can be expressed with a real language.

What is the point for "ParameterNameRequirement.Required" for example? This is essentially a bool option with a long name. The fundamental problem is not that it looks like someone left a turd on my source code or the fact that it is a glorified bool value. The problem is that enumerations and an OO structure will not give you the flexibility that is required for command line handling. This API would not be able to cope magically with conflicting options (either -a or -b can be used) or with options that are required if another option is set (if -v set, then -log is needed) or with custom parsing required after the basic command line options are parsed (consider a C compiler, -I and -L and -l options can be specified multiple times).

This is the equivalent code in Mono.Options. Notice that the policy can be enforced either outside of the parameter parsing (after the basic parsing has been done) or as each one of the delegates for the options:

	using System;
	using Mono.Options;

	class Program {
	    static void ShowHelp (string msg, OptionSet p)
	    {
	        p.WriteOptionDescriptions (Console.Error);
	        Console.Error.WriteLine (msg);
	    }

	    static void Main (string [] args)
	    {
	        Console.WriteLine("Checks a disk and displays a status report.\n");

	        bool fix = false;
	        int logSize = 1024;
	        string volume = null;

	        OptionSet p = new OptionSet ()
	            .Add ("volume=", "Specifies the drive letter.", v => volume = v)
	            .Add ("f|F",  "Fixes error on the disk", v => fix = true)
	            .Add ("l=", "Changes the log file size to the specified number of kilobytes",
	                  v => logSize = int.Parse (v));

	        try {
	            p.Parse (args);
	        }
	        catch (OptionException)
	        {
	            ShowHelp ("Error, usage is:", p);
	        }
	        if (volume == null)
	            ShowHelp ("Error: must specify volume", p);

	        Console.WriteLine("Checking volume {0}...", volume);

	        if (fix)
	            Console.WriteLine("Fixing errors...");

	        if (logSize != null)
	            Console.WriteLine("Changing log size to {0}...", logSize);
	        else
	            Console.WriteLine("Current log size: {0}", 1024);
	    }
	}
	

The number of lines is roughly the same, but one is an eye sore and limited. The other is both beautiful and extensible.

Both APIs are capable of more. But System.Shell.CommandLine will merely give you more enumerations with limited functions and you will end up rolling out your own if you want to do anything remotely interesting.

Mono.Options is more of an open ended, future-proof and extensible API. It is implemented as a single C# source file (1,112 lines of code) that you can use in your own projects (and even tune/modify), you can start using today and will work on .NET 2.0 and up, it is nicely documented. More examples: here, here and here.

System.Shell.CommandLine does not belong in System.Core. System.Core is a fundamental assembly that will be everywhere .NET is, and it will soon enough run into the same upgrade and maintenance restrictions that mscorlib has.

This API needs to be moved into CodePlex or be available as an unsupported "PowerOverEngineeredPack.dll".

Posted on 21 Feb 2009 by Miguel de Icaza
This is a personal web page. Things said here do not represent the position of my employer.