Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Command Line Written in C#. Part 2: Attribute Arguments.

0.00/5 (No votes)
5 Aug 2003 2  
Accessing a command line data formatted as an attribute argument list.

Introduction

Obviously, a program starts with Main(string[] args) where args usually has to be somehow parsed and evaluated. There are many solutions of this simple but sometimes boring and non-creative task. Part 1 and 2 of this article propose two more different solutions. The peculiarity is that they do not parse anything themselves, they are entirely based on C# and .NET capabilities.

In Part 1, a command line argument is treated as a piece of C# code accessing program stuff. A command line becomes a real part of the program and it is written in C#, like a program. This approach is very powerful for developers, especially for debugging and testing. But for an end user, this may be inconvenient and even unsafe.

Part 2 proposes an approach designed exactly for an end user. A command line looks almost like a traditional one. How does more or less "traditional" command line look like? Let say, it has a number of required positional arguments and a number of optional named arguments. It reminds of a C# attribute argument list and leads to the idea: let a command line consists of one argument, which in fact is a list of arguments of an attribute. Our aim is to access the data with minimal programming effort.

Step 1: Input data design

Let's talk in terms of a hypothetical example program (source code is attached). Suppose our program expects the following input data:

  • Required string value (for example, some directory name);
  • Required bit field (for example, flags of some jobs to do);
  • Some optional int value;
  • Some optional bool value;
  • Some optional double value.

We are going to use an attribute arguments style for the args[0] value in the Main().

Example of args[0] values:

null, Jobs.Job1|Jobs.Job2
@"C:\Temp", Jobs.Job2|Jobs.Job3, MyDouble = 3.14
@"C:\Temp", Jobs.Job3, MyBool = true, MyDouble = 3.14, MyInt = 123

Note: the command line consists of the only argument, which has to be entirely enclosed in quotation marks (") and any inner quotation mark has to be preceded by (\). These requirements come from the command line rules.

Command lines corresponding to args[0] above:

"null, Jobs.Job1|Jobs.Job2"
"@\"C:\Temp\", Jobs.Job2|Jobs.Job3, MyDouble = 3.14"
"@\"C:\Temp\", Jobs.Job3, MyBool = true, MyDouble = 3.14, MyInt = 123"

Step 2: Input data class

Having designed input data, we create a class to keep them. The class InputAttribute is derived from System.Attribute. Required data (directory name and job flags) are implemented as read only properties and a constructor taking them as parameters is added to the class. Optional data are implemented as read/write properties; their names in a real program have to be well thought-out to describe the meaning well enough. Exactly these names are used in a command line by a user. Finally, we define enum Jobs whose values are also used in a command line.

namespace Test
{
    // This enum is used in the command line

    [FlagsAttribute()]
    public enum Jobs { Job1 = 1, Job2 = 2, Job3 = 4 }

    // Command line data are evaluated as this class field values

    [AttributeUsage(AttributeTargets.Assembly)]
    public sealed class InputAttribute : System.Attribute
    {
        // Constructor: 'directory' and 'jobs'

        // are positional and required arguments

        public InputAttribute(string directory, Jobs jobs)
        {
            this.directory = directory;
            this.jobs = jobs;
        }

        // Required positional: 1st argument

        public string Directory
        {
            get { return directory; }
        }
        
        // Required positional: 2nd argument

        public Jobs Jobs
        {
            get { return jobs; }
        }
        
        // Optional named: MyInt=...

        public int MyInt
        {
            get { return myInt; }
            set { myInt = value; }
        }

        // Optional named: MyBool=...

        public bool MyBool
        {
            get { return myBool; }
            set { myBool = value; }
        }

        // Optional named: MyDouble=...

        public double MyDouble
        {
            get { return myDouble; }
            set { myDouble = value; }
        }

        // These data are required by constructor

        private string directory;
        private Jobs jobs;
        
        // These data are optional with some default values

        private int myInt = 0;
        private bool myBool = false;
        private double myDouble = 0.0;
    }
}

Step 3: Input data object

Now we are ready to write the only code statement to get the command line data:

InputAttribute input = (InputAttribute)Attributer.Evaluate(
    typeof(InputAttribute), args[0], "using Test;");

Where:

  • typeof(InputAttribute) is the type of our attribute class;
  • args[0] is the command line argument of our program;
  • "using Test;" is used for brevity to allow writing "Jobs.Job1" instead of "Test.Jobs.Job1" in the command line.

The code

The class Absolute.Attributer is an attribute evaluator (source code is attached). It is a "static class", it has the only a static method Evaluate(). This method performs following three small steps:

  1. An assembly source code is generated. It contains using directives (if needed), an assembly attribute and no other code. An example of the generated source code:
    using Test;
    [assembly:InputAttribute(@"C:\Temp", Jobs.Job1, 
                      MyDouble = 3.14, MyBool = true)]
  2. The assembly source is compiled and the assembly is created in memory. This assembly contains nothing but our attribute InputAttribute.
  3. An instance of the InputAttribute is requested and returned. This instance contains all our data evaluated and ready to use.

A word about safety

In contrast with Part 1 of the article (execution of a piece of C# code coming from a command line), this way seems safe for an end user. By definition, attribute arguments cannot be non-constant expressions. If a careless or malicious non-constant expression is written as an attribute argument, then it cannot be compiled. Step 2 (see Background) will fail at compile time and an exception will be thrown. You can try to write different "bad" command lines for our example program, run it and see what happens. You will get an error message and the program will stop. Nevertheless, could someone find the joint in the armor?

Summary

The attached project contains two files: Attributer.cs (utility class) and Class1.cs (example program described here). I hope this simple utility will help someone to avoid non-creative command lines parsing work in his own programs. Any remarks are welcome.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here