Tips for simpler programming in C#: Part III

 

Command line tool syntax

Command line interface (CUI) designs for many of the tools found with VS or in Windows were to include a bunch of individual commands or exe with options or flags.
To get a directory listing: dir <options> as in dir c:\temp /w
To create a directory: md c:\temp\foo
To delete a directory: rd /s c:\temp\foo

So even though all the tasks pertain to directory or path handling, you need to know three commands to perform the task. This design is slowly changing to the format of: command <action> [</options:attributes>]

For example, if we change the above commands to this format with the common command called dir, it will be:
To get a directory listing: DIR list c:\temp /w
To create a directory: DIR make c:\temp\foo
To delete a directory: DIR rem c:\temp\foo /s

So, user learning is effectively reduced. One can argue that he still needs to remember the names of the actions. But in the previous case he had to remember rd, md and dir. Now he knows that everything to do with path handling can be done with DIR. Thus, he can run DIR /? to get the list of actions and then do a DIR list /? to know the options for listing. The other advantage is, namespace pollution (in terms of names of the executables) also reduces.

This new format has been adopted in VS as in the source control tool. To get the listing of all workspaces on MyServer you run the command:
h.exe workspaces /s:MyServer

To create a new workspace, you run the command:
h.exe workspace /new MySpace /s:abhinab-test

I guess going forward we will see more and more of the standard OS commands also taking up this format. A very common example is the NET command.

.NET APIs are not always honest
First a quiz. Assuming that all the required privileges are there, what will be the output of code 1? All the important bits are marked in bold.

Code 1

class Program
{
static void Main(string[] args)
{
string dirName = @"c:\a.";
try
{
System.IO.Directory.CreateDirectory(dirName); 
Console.WriteLine("Created {0}", dirName);
Console.WriteLine( System.IO.Directory.Exists(dirName) ? 
"Dir exist": "Dir doesn't exist");
}
catch (Exception ex)
{
Console.WriteLine("Failed to create directory {0}: {1}", 
dirName, ex.Message);
}
}


The output is:
Created c:\a.
Dir exist


The problem is, if I go to C:\ I see a folder named a and not a. The dot at the end is conveniently dropped. What’s more interesting is that even though a. does not exist, the Directory.Exists API returns true. Windows does not support files and folders with a dot at the end. If you use command window to create a file with a dot at the end, then you simply get the file without the dot and no error is reported. .NET Directory APIs simulate this to the last letter, but the question is do I want this simulation? I would like to see APIs being honest and report issues if it fails to do something. APIs are used in a lot of places and the burden of checking should be done inside the API and not on code calling the API.

When you create a Build Type using the Wizard, the user can enter names with a DOT at the end. We then create a folder with that name and check the whole thing into Team Foundation Source Control. The problem is, what gets created is a and not a. So even though you wanted to create a Build Type named a. you have one named a and all sorts of weird things happen after that. Though this is kind-of a corner situation, still I would prefer a more honest API anytime.

The CLR Nullable DCR works!!!!!

CLR took a DCR (Design Change Request) some time back on how nullable types are implemented. I found from one of the internal DLs that the fix was already in the RTM bits.

I wrote a small piece of code that should work only with the DCR changes and kept it aside so once this DCR gets implemented I would try it out. So each time I upgraded to a new build of VS/CLR, I built/ran this code to see if it works. And it did. The new nullable type, after becoming a basic runtime intrinsic, just rocks. I have put in comment all the failures that used to happen previously with the same piece of code. Check out code 2.

Code 2

// this code works only with the latest bits of 
// Visual Studio 2005 (C#2.0) and will fail to 
// compile/run with older Beta2 bits
static void Main(string[] args)
{
int? val = 1;
int? nullval = null;

// this always worked
if (nullval == null)
Console.WriteLine("It worked");

// this used to fail before and I think one of 
// the biggest issue with the previous implementation
object nullobj = nullval;
if(nullobj == null)
Console.WriteLine("It worked");

// previously required 
// box = Nullable.ToObject<int>(val); argh!!!!
object box = val;

// If you do not need exceptional code 
// (that's code that throws exception)
// you needed to do int? unbox = Nullable.FromObject<int>(box);
int? unbox = (int?)box;
if (unbox == val)
Console.WriteLine("It worked again");

int intval = (int)box;
if (intval == val.Value)
Console.WriteLine("attained Nullable Nirvana");

int x = 10;
object y = x;
// Previously Exception!!! cast is not valid
int? z = (int?)y;

IComparable c = val;
// Previously Exception!!! : object type cannot be 
// converted to target type
if(c.CompareTo(box) == 0)
Console.WriteLine("I have seen it all");

// compilation failure
IComparable<int> ic = val;
if(ic.CompareTo((int)box) == 0)
Console.WriteLine("Wow there is even more to see");
}


The output of the program is:
It worked
It worked
It worked again
attained Nullable Nirvana
I have seen it all
Wow there is even more to see

Its not a good idea to use Console colors too much

With Whidbey (VS2005), managed Console-based applications have got a boost with new additions to the System.Console namespace. You can now change the color, size, buffer size, cursor size, cursor position and window title directly from a managed application without PInvoke.

However, just because it can be done easily does not mean that it should be used. If you are developing a console-based game or something similar, like a chat client, you have the liberty to do this. Otherwise, think twice (or thrice) before you play around with any of these settings. The reason is simple. Most people (like me) will get super annoyed if for some reason the console window suddenly jumps and resizes when I run a program.

If you think that doing something simpler like changing the text color (using Console.ForegroundColor) to draw attention is Ok, consider it carefully. It might just not work; could look horrible on some specific console setting or might convey the wrong meaning. I have listed below some common usages of changing these setting programmatically and why they might not work. Most programmers agree and understand that playing with the console window size or buffer size is not a good idea and do not use it.

a. Changing text color using Console.ForegroundColor

Warning and Error Messages: Using different colors for output text, like yellow for warnings and red for error might be fine. You just need to ensure that you use the right set of colors as their meanings are deeply bound in the mind of the users. The following table typically works:
· Yellow: warning;
· Red: Error;
· Green: Pass, success messages; and
· Cyan: Information.

Do NOT use colors unless absolutely essential: It is best to avoid using colors when not absolutely required. In most cases it will lead to trouble and rarely be of any benefit. See the following example where the prompt comes in yellow. Since this is a console-based application, it would have blocked until I entered the required inputs. So, drawing attention with yellow does not add any value. Since magenta is associated with warning, a first-time user might think that something is wrong and he is required to rectify this by entering some server name.

Welcome to my tool
Server : MyServer
Username: Abhinaba

Do NOT use a dark color for text: People do change the background color of the console. I often use Teal and DarkBlue, so ensure that you do not choose one of the darker colors for the text as it might coincide with the background color of the console window and your text will be invisible. I once came across a tool that used blue for text entered by the user. I had launched that application on a blue background console. Since I was using the application for the first time, I had a hard time figuring out what was going on, as I could not see what I was typing.

The safe colors are Gray, Green, Cyan, Red, Magenta, Yellow and White. However, sometimes even these colors, in combination with background colors, are an eyesore as in
Some error message!!

Do restore the original color: Even if you use any of the above colors, remember to switch the color back to the original color. Use some thing like:

public void ShowError(string message)
{
ConsoleColor orgCol = Console.ForegroundColor; 
Console.ForegroundColor= ConsoleColor.Red;
Console.WriteLine(message);
Console.ForegroundColor = orgCol; // restore color
}


This might look trivial, but can be a big source of annoyance. Like your application gets an exception, the exception-handler sets color to red, shows the error message and the application exits. Now, whatever I type in that console window is in Red!!! 

b. Using Console.BackgroundColor

Do NOT change the background color: Console.BackgroundColor = ... is bad. The reason being that the color is changed only for the subsequent text and it looks horrible as shown below:
Text before color change
Text after I changed the background col to cyan

Even though very few people intentionally do this, it does creep into an application. Sometime back, in some error message code I saw this:
Console.BackgroundColor = ConsoleColor.Black;
Console.ForegroundColor = ConsoleColor.Red;
Console.WriteLine("Error!!!");

This went undetected in tests, until someone opened it in a Teal window and this is what came up:

Some error message!!

c. Laying with cursor

CursorVisible: You might want to hide the cursor when showing some output using Console.CursorVisible = false. Just remember to revert it back to the same visibility state as it originally was.

CursorPosition: Jumping the cursor around the screen is not a mainstream scenario and most console applications do not use it. However, I have seen some tools like build-systems use this to show subsequent outputs in the same line. Consider the following code:

int currPos = Console.CursorTop; // get the current pos 
for (int i = 0; i < 30; i++) 
{
Console.WriteLine("Counting {0}", i); 
Console.CursorTop = currPos; // Go back to the same line
System.Threading.Thread.Sleep(200);
}


This shows the counter in the same line. This might be required in your application. However, in some cases its overdone and you can simply live without it...

Most programmers are used to UI guidelines. Unfortunately, most of these guidelines ignore command-line interface and are silent on them.

The author is available at: abhinaba@gmail.com




Added on May 31, 2007 Comment

Comments

Post a comment