Click here to Skip to main content
15,860,859 members
Articles / Programming Languages / C#
Article

Command Line Written in C#

Rate me:
Please Sign up or sign in to vote.
4.67/5 (10 votes)
18 Jul 20034 min read 120K   50   9
The article is about how to use command lines written in C#.

Introduction

Actually I am mostly a C++ developer, but I found that C# with .NET framework is a perfect tool for my frequent auxiliary jobs like, log files and data dump files analyses and other similar tasks. Thus, I have written a number of console utilities in C#. It is quite normal that each Main() function of these utilities does command line parsing, arguments validation and conversion into proper types, decides which method has to be called and then calls this method with prepared parameter values.

But I am tired of composing each command line rules and of writing a boring code that follows these rules. And I am tired of updating these rules and corresponding code, every time after adding or changing utility abilities. I have found a solution. From this time onwards, I prefer to write a direct call to a method with necessary parameter values just in a command line. For example: test.exe "Job1(1, Math.Sqrt(3.14), new MyClass())" or "Job2(@\"C:\Temp\", true)" where Job1() and Job2() are existing public methods. I do not write a line of code to parse command lines in my utilities any more. Thanks C# and .NET!

What can you do with the Accessor class?

  • You can evaluate C# string expression or execute a method text at runtime; note that these expressions and methods can freely access all existing public members in the main assembly (or in any other additional, if specified);
  • My proposal is to apply this ability to evaluate or execute a command line in the Main()! As you have access to public members of the main assembly, you can invoke any method that was designed for that. A command line becomes a real part of your C# program and it is written also in C#.
  • Although I have started the article with words about console applications, you can apply this technique for Win Forms as well.

Your benefits if the Main() evaluates a command line

  • If you change or extend the program functionality, then you do not change Main() any more. You can freely call new or changed methods just from a command line.
  • You can use this effectively for debugging. Do you have a just created method not yet debugged? Do you want to invoke it immediately? Just write the call to this method in the command line with some parameter values and run debugging.
  • You can use this effectively for testing. You can call for testing, all public methods of your program just from a command line. The simplest way is a batch file with multiple runs of your program with different command lines, read: different calls to different methods.

A word about safety

This approach is not a toy for a final user! It is not a good idea to give a customer the ability to call any public method in your program from a command line. This approach is mostly for software developers themselves, just to eliminate a lot of simple but very boring and non-creative work.

Nevertheless, there are different quite safe ways to keep and use this ability in a released application. You can design your application so that your main assembly has only the methods intended for calling from a command line. These methods themselves have access to the other application assemblies, but a source written in command line has to be referenced to the main assembly, only in order to access the methods designed for that. A command line source compilation will fail on attempting to access something else which is not allowed. Of course it is up to a developer how to protect his application from dummies or hackers.

The code

The code below has just to be added to your code to make it all possible. It defines the class Accessor and also contains examples of how it works (namespace Test, see 3 examples in Test.Example.Main()). You have to define the EXAMPLE constant or you may just replace it with true to enable the example code. It seems I have nothing to say here any more, the code and comments say the rest. Note that Accessor can also be used as expressions evaluator and methods executor.

C#
using Microsoft.CSharp;
using System.CodeDom.Compiler;
using System.Diagnostics;
using System.Reflection;
using System.Text;
using System;

// The example illustrates main purposes of Accessor:
// 1) Evaluation of a string expression
// 2) Execution of a method's code
// 3) Execution of a command line written in C#
#if EXAMPLE
namespace Test
{
    // This class is used to show that:
    // -  we can freely create an instance of MyClass in our string source,
    //    it means that we can create any other public class of this
    //    assembly and access its public members;
    // -  we can access even the protected MyMethod() in our string source,
    //    it means that we can also access any protected members of
    //    a public not sealed class of this assembly.
    public class MyClass
    {
        public MyClass()
        {
            Console.WriteLine("Hello from Test.MyClass()");
        }
        protected int MyMethod()
        {
            Console.WriteLine("Hello from Test.MyClass.MyMethod()");
            return 12345;
        }
    }

    // Main class
    public class Example
    {
        // Suppose Job1() and Job2() implement this program functionality.
        // Then we can just write command lines like:
        //      test.exe "Job1(0, 0.0, null)"
        //      test.exe "Job1(1, Math.Sqrt(3.14), new MyClass())"
        //      test.exe "Job2(@\"C:\Temp\", true)"
        //      test.exe "Job2(@\"C:\Documents and Settings\" , false)"
        // Note: just try these command lines
        public int Job1(int arg1, double arg2, MyClass arg3)
        {
            Console.WriteLine
                ("Hello from Job1() with {0}, {1}, {2}", arg1, arg2, arg3);
            return 0;
        }
        public int Job2(string arg1, bool arg2)
        {
            Console.WriteLine
                ("Hello from Job2() with {0}, {1}", arg1, arg2);
            return 0;
        }

        // Here there are 3 examples of 3 main purposes of Accessor
        static int Main(string[] args)
        {
            // Example 1. Evaluation of a string expression.
            string myExpression = "M.Sin(0.5) + M.Sqrt(2.0)";
            Console.WriteLine("\nExample 1. Evaluating:\n{0}...", 
                                                    myExpression);
            double result1 = (double)Absolute.Accessor.Evaluate(
                myExpression,
                false, // we don't need main assembly reference
                // just for fun: M instead of Math
                "using M = System.Math;", 
                null); // we don't need any class base list
            Console.WriteLine("Evaluation result: {0}", result1);

            // Example 2. Execution of a method text.
            string myMethod = @"
int Executable()
{
    Console.WriteLine(""Hello from Executable()"");
    return MyMethod();
}";
            Console.WriteLine("\nExample 2. Executing method:{0}...", 
                                                           myMethod);
            int result2 = (int)Absolute.Accessor.Execute(
                myMethod,
                true, // add main assembly reference to access MyClass
                // we can use MyClass instead of Test.MyClass
                "using Test;", 
                // MyClass's child can access protected MyMethod()
                "MyClass"); 
            Console.WriteLine("Execution result: {0}", result2);

            // Example 3.
        
            // THE AIM IS TO USE OUR PROGRAM AS A COMMAND LINE DRIVEN TOOL
            // WITHOUT WRITING A LINE OF CODE THAT PARSES ITS COMMAND LINE
            // Let the very first argument be a piece of code invoking
            // a program job and let Accessor make all parsing for us!

            // Our benefits for this example:
            // - we don't validate arguments and convert 
            //   them into required types
            // - we don't write any code to create required 
            //   MyClass instance
            // - we don't even call Job1() or Job2() methods;
            // - if (oh God!) we change Job's parametes count or types
            //   then this Main() function always remains the same
            //   (as usually we have to use updated command lines)
        
            int jobResult;
            try
            {// we have to catch possible compilation and execution errors
                // args0 is args[0] if any else some default job
                string args0 =
                    args.Length > 0 ?
                    args[0] : "Job1(1, Math.Sqrt(3.14), new MyClass())";
                Console.WriteLine
                    ("\nExample 3. Running command line:\n\"{0}\"", args0);
                jobResult = (int)Absolute.Accessor.Evaluate(
                    args0,
                    true, // access to Example and MyClass
                    "using Test;", // MyClass istead of Test.MyClass
                    "Example"); // access to Job()
                Console.WriteLine("Command line result: {0}", jobResult);
            }
            catch(Exception e)
            {// syntax errors are reported here as well as any others 
                Console.WriteLine(e.Message);
                jobResult = -1;
            }

            // result
            return jobResult;
        }
    }
}
#endif

namespace Absolute
{
    // summary: C# expessions evaluator and methods executor
    // remarks: Author: Roman Kuzmin
    public sealed class Accessor
    {
        // summary: Evaluates C# expression
        // expression: C# expression
        // mainAssembly: Add a reference to the main assembly
        // headCode: null or a code to be added at a generated source head
        // classBase: null or a generated class base list
        // assemblies: [params] null or additional assemblies
        // returns: Evaluated value as object
        public static object Evaluate(
            string expression,
            bool mainAssembly,
            string headCode,
            string classBase,
            params string[] assemblies)
        {
            string methodCode = String.Format(
                "object Expression()\n{{return {0};}}", expression);
            return Execute(
                methodCode, mainAssembly, headCode, classBase, assemblies); 
        }
        
        // summary: Executes C# method's code
        // methodCode: Complete C# method's code
        // mainAssembly: Add a reference to the main assembly
        // headCode: null or a code to be added at a generated source head
        // classBase: null or a generated class base list
        // assemblies: [params] null or additional assemblies
        // returns: null or the executed method's return value
        public static object Execute(
            string methodCode,
            bool mainAssembly,
            string headCode,
            string classBase,
            params string[] assemblies)
        {
            // extract method name
            int i1, i2 = methodCode.IndexOf('(');
            if(i2 < 1)
                throw new Exception("Accessor: syntax error: ( expected");
            for(--i2; i2 >= 0 && methodCode[i2] <= ' '; --i2);
            for(i1 = i2; i1 >= 0 && methodCode[i1] > ' '; --i1);
            string methodName = methodCode.Substring(i1 + 1, i2 - i1);

            // code builder
            StringBuilder code = new StringBuilder();

            // << default using directives
            code.Append("using System;\n");
            
            // << head code
            if(headCode != null)
                code.AppendFormat("{0}\n", headCode);

            // << class name
            code.Append("public class AbsoluteClass");

            // << clase base list
            if(classBase != null)
                code.AppendFormat(" : {0}", classBase);

            // << class body with method
            code.AppendFormat("\n{{\npublic {0}\n}}\n", methodCode);

            // compiler parameters
            CompilerParameters parameters = new CompilerParameters();
            parameters.GenerateExecutable = false;
            parameters.GenerateInMemory = true;

            // referenced asseblies
            parameters.ReferencedAssemblies.Add("system.dll");
            if(mainAssembly)
                parameters.ReferencedAssemblies.Add(
                    Process.GetCurrentProcess().MainModule.FileName);
            if(assemblies != null)
                parameters.ReferencedAssemblies.AddRange(assemblies);

            // compiler and compilation
            ICodeCompiler compiler =
                new CSharpCodeProvider().CreateCompiler();
            CompilerResults results =
                compiler.CompileAssemblyFromSource(
                parameters, code.ToString());
            
            // errors?
            if(results.Errors.HasErrors)
            {
                StringBuilder error = new StringBuilder();
                error.Append("Accessor: compilation errors:\n");
                foreach(CompilerError err in results.Errors)
                    error.AppendFormat(
                        "#{0} {1}. Line {2}.\n",
                        err.ErrorNumber, err.ErrorText, err.Line);
                error.AppendFormat("\n{0}", code);
                throw new Exception(error.ToString());
            }

            // class instance
            object classInstance =
                results.CompiledAssembly.CreateInstance("AbsoluteClass");

            // execute method
            MethodInfo info = classInstance.GetType().GetMethod(methodName);
            return info.Invoke(classInstance, null);
        }

        // summary: Private constructor makes the class 'pure static'
        private Accessor()
        {
        }
    }
}

History

  • 07/20/2003 - Original article.
  • 07/21/2003 - More detailed explanation and code comments.

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


Written By
Software Developer (Senior)
United Kingdom United Kingdom
Roman Kuzmin is working as a software developer. Programming experience most specifically includes C\C++, MFC, C# and .NET framework. Scientific experience is in the fields of 3D modeling and reverse engineering, parametric curves and surfaces, approximation, non-linear minimization and non-linear equation systems.

Comments and Discussions

 
GeneralMaybe you'd like to join... Pin
Marc Clifton22-Jul-03 17:21
mvaMarc Clifton22-Jul-03 17:21 
GeneralConfused (as always) Pin
dog_spawn20-Jul-03 2:53
dog_spawn20-Jul-03 2:53 
GeneralRe: Confused (as always) Pin
Jörgen Sigvardsson20-Jul-03 6:28
Jörgen Sigvardsson20-Jul-03 6:28 
GeneralRequested another example Pin
Roman Kuzmin20-Jul-03 8:34
Roman Kuzmin20-Jul-03 8:34 
GeneralRe: Confused (as always) Requested another example Pin
dog_spawn20-Jul-03 8:50
dog_spawn20-Jul-03 8:50 
GeneralIt depends on the kind of customers Pin
Roman Kuzmin20-Jul-03 14:58
Roman Kuzmin20-Jul-03 14:58 
GeneralThe article has been updated Pin
Roman Kuzmin21-Jul-03 3:01
Roman Kuzmin21-Jul-03 3:01 
Questionmodify for using in winform app ? Pin
BillWoodruff19-Jul-03 22:54
professionalBillWoodruff19-Jul-03 22:54 
Hi,

This is an interesting and useful article ! Thanks.

I am wondering if this could be extended to be used in a WinForms app where I would use it as a way of querying the app about parameters of current objects and their values.

Appreciate any thoughts you have on this.

best, Bill Woodruff

"The greater the social and cultural distances between people, the more magical the light that can spring from their contact." Milan Kundera in Testaments Trahis
AnswerNo modifications are required Pin
Roman Kuzmin20-Jul-03 15:42
Roman Kuzmin20-Jul-03 15:42 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.