Skip navigation

Program Efficiently

Use declarative programming to streamline your work.

aspnetNOW

 

Program Efficiently

Use declarative programming to streamline your work.

 

By Jeffrey Richter

 

Programming is hard work and often can be quite tedious. I can't count the number of times I've written a linked list (or some other algorithm) over and over again and added just a few tweaks to the code here and there. Obviously I'm not alone because many new technologies have appeared over the years to help developers. I'm sure you're familiar with object-oriented programming as a technology that allows one developer to design and implement an algorithm in a generic fashion; then, other developers can tweak the generic algorithm for a specific situation (by deriving from a base class and overriding virtual methods). No developer questions the incredible productivity boost object-oriented programming provides - it has proven to be a huge benefit, especially when you design and implement big applications.

 

For Windows programming, C# has made object-oriented programming mainstream because it makes building Windows applications and components more efficient. C# brings, however, another technology that, for some reason, doesn't enjoy as much of the limelight as object-oriented programming. This other technology is known as declarative programming, which is when you instruct your application or component to do something using data rather than by writing source code. (The act of writing source code is sometimes called imperative programming.)

 

Go Mainstream

An example of declarative programming is the production of an HTML Web page. In this process, the "programmer" defines how the Internet browser application should process the HTML tags and text to lay out the page in a window. Many hard-core programmers don't consider HTML programming to be "real programming." But I do, and I think this kind of declarative programming is going to become much more mainstream - thanks to C#.

 

C# offers many forms of declarative programming. First, there's metadata - data produced by the C# compiler as it processes your source code and is embedded in the resulting .exe or .dll file. This declarative data is used by several facilities that are part of the .NET Framework and its Framework Class Library (FCL). For example, the serialization formatters - BinaryFormatter and SoapFormatter - classes can discover automatically the fields within an object and fields that refer to other objects by examining the object type's metadata. From this declarative information, the formatters easily can walk an entire object graph spitting out a stream of binary bytes or a stream of SOAP.

 

Imagine how you would have to implement serialization if metadata didn't exist. Without metadata, Microsoft wouldn't have been able to create the BinaryFormatter or SoapFormatter classes because there would be no way for these classes to obtain enough information about an object graph to do anything useful with it. Instead, if you needed serialization in your own application, you would have to implement the serialization code manually and the implementation probably would be specific to the types of objects your application needs to serialize. Maintaining the code for this application as your application's types change would be quite an undertaking.

 

In addition, without metadata, the code to serialize the object graph would be sprinkled through your application's types; there's no "serialization algorithm," per se. The code to serialize one type is in one source-code file, the code to serialize another type is in another source-code file, and so on. This means similar code frequently is copied from one place to another and, therefore, the "algorithm" has multiple maintenance points.

 

Infuse Your Source-Code Files

Earlier technologies introduced the notion of declarative programming. Many years ago, an Interface Definition Language (IDL) was created to help you call a method whose code was on another machine. This is referred to as a Remote Procedure Call (RPC). You would create an IDL file containing the definitions of the interfaces (functions) that could be called remotely. Then, a special compiler such as Microsoft's MIDL.exe would parse the IDL file. This compiler would produce the client stub code, the server stub code, and a header file the client code could use to make the remote call.

 

Having IDL files and the MIDL.exe compiler is fantastic for programmer efficiency and productivity because IDL lets you describe how to call a function remotely, and the IDL compiler is endowed with a generalized algorithm that knows how to spit out the complex code for exposing and calling remotable procedures. So, IDL files are a form of declarative programming.

 

Unfortunately, this form of declarative programming has a major drawback: The declarative information is separate and disjointed from the actual code implementing the remotely callable function. For programmers, this causes all kinds of trouble. For example, if you change the prototype of a function, you also must remember to update the IDL file and rerun the IDL compiler so the stub code is in sync with the most recent version of the function's prototype. I can't remember how many times I was stung by this kind of "bug" and I'd like to forget how many hours I wasted debugging these kinds of problems.

 

Fortunately, the C# team came up with a way to add declarative programming directly inside your source-code files. In other words, you can add custom declarative information right in your source-code files. Then, when you compile your source code, the C# compiler parses the declarative information and embeds it in the resulting metadata (the other declarative information produced automatically by the compiler itself). This produces several huge benefits for programmers.

 

First, your intended declarative information is in the same file (and even in the same location within the file) as the code to which the declarative information applies. This means you are far less likely to forget to modify the declarative information if you make a change to the source code itself. Second, there's only one syntax to learn instead of learning C# syntax, IDL syntax, and so on. Third, there's only one compiler to run, and it compiles both your source code and the declarative information simultaneously. Finally, because the compiler compiles both the source code and the declarative information at the same time, it's not possible for the information to be out of sync and, because the compiler emits all this information into the resulting .exe or .dll file's metadata, the code that implements the types and methods always will be in sync with the metadata and other declarative information. You'll no longer waste countless hours debugging because the declarative information was out of sync with the implementation, or because you forgot to recompile the declarative information, or because you used an old version of the declarative information.

 

In C#, a custom attribute identifies information you can apply declaratively to a piece of source code. A custom attribute is simply a class (like any other class) except it's derived from System.Attribute. And, generally, a custom attribute class defines some constructors, some fields, and maybe some properties, but no other methods or events. Figure 1 defines the DllAttributeClass.

 


Figure 1. This sample code listing highlights a constructor and fields.

 

Basically, think of a custom attribute class as a state holder. In this article, I don't have the space to explain fully what a custom attribute class is or how to define it, apply it, and look for its existence programmatically. So I'll assume you're somewhat familiar with this process or know of some good references. Naturally, I'd like to recommend my own book, Applied Microsoft .NET Framework Programming (Microsoft Press); Chapter 16 focuses on custom attributes.

 

Define it and Apply it

Once you define a custom attribute class, you can apply an instance of it to a piece of source code. In C#, you can apply custom attributes to an assembly, module, type (class, struct, interface, or delegate), property, event, field, method, method parameter, or a method's return value (see Figure 2).

 

using System;

 

[assembly:Xxx]   // Applied to the assembly

[module:Xxx]     // Applied to the module

 

[type:Xxx]      // Applied to the type

class SomeType {

 

    [property:Xxx]   // Applied to the property

   public String SomeProp { get { return null; } }

 

    [event:Xxx]     // Applied to the event

   public event EventHandler SomeEvent;

 

    [field:Xxx]     // Applied to the field

   public Int32 SomeField = 0;

 

    [return:Xxx]    // Applied to the return value

    [method:Xxx]    // Applied to the method

   public Int32 SomeMethod(

       [param:Xxx] // Applied to the parameter

      Int32 SomeParam) { return SomeParam; }

}

Figure 2. This sample shows where you can apply custom attributes.

 

The .NET Framework Class Library (FCL) defines many custom attributes you can apply to items in your own source code. Here are some examples:

 

  • Applying the DllImport attribute to a method informs the CLR that the implementation of the method actually is located in unmanaged code contained in the specified .dll
  • Applying the Serializable attribute to a type informs the serialization formatters that instances of the type can be serialized to binary or SOAP
  • Applying the AssemblyVersion attribute to an assembly sets the version number of the assembly
  • Applying the ParamsArray attribute to a parameter tells compilers that the method accepts a variable number of arguments
  • Applying the WebMethod attribute to a public method tells ASP.NET that the method is an XML Web Service method callable remotely over the Internet

 

The FCL is filled with literally hundreds of custom attributes; the previous list is an extremely small sampling that demonstrates the most popular uses.

 

Now that you have a feel for declarative programming and some examples of how Microsoft has defined some custom attributes, I'd like to share with you an idea of my own.

 

In my day-to-day work, I write many small utility programs, and almost all of them require the end user to specify command-line arguments. This means all my applications contain code to parse command-line attributes. One day I was pondering how I could make it easier to parse command-line arguments, and it dawned on me: declarative programming. I'll design my own CmdArgAttribute custom attribute that'll make it trivially simple for me to add command-line argument parsing to all my programs. This article's code download defines my custom attribute, checks for its existence, and demonstrates how to use it (see the Download box for details). You'll probably want to refer to the source code while reading the remainder of this article.

 

Start it Up

Let me explain how I (or you) would start using my custom attribute. First, define the usage for my application. The code download is a simple utility that creates a file or appends to an existing file, writing some text to the file several times. The program isn't particularly interesting (or useful in real life), but it does demonstrate how to use my custom attribute to parse command-line arguments. If you run the program specifying the /? switch, this usage information appears:

 

Usage: WriteTextToFile /P:Pathname /T:String

           [/M:Create|Append] [/N:NumTimes]

   /P         The pathname of the file to be created or

              appended to

   /T         The text to write to the file

   /M:Create  Create the file (if the file already exists,

              it is erased). This is the default.

   /M:Append  Append to an already existing file (if the

               file doesn't exist it is created)

   /N         The number of times to write the string to

              the file (1 if not specified)

   /?          Show this usage help

 

Normally, writing the code to parse the command-line options would be tedious and error-prone. But using my CmdArgAttribute makes it incredibly simple. Once the usage for the application is defined, the second step is to define a data structure that contains a field for each of the application's possible command-line arguments. Then, apply my CmdArgAttribute to each of the fields (see Figure 3).

 

enum WriteMode { Create, Append }

 

// The fields of this class map to

// command-line argument switches.

class Options : ICmdArgs {

   // Members identifying command-line argument settings

    [CmdArg(ArgName = "P", RequiredArg = true,

       RequiredValue = CmdArgRequiredValue.Yes)]

   public String pathname = null;

 

    [CmdArg(ArgName = "T", RequiredArg = true,

       RequiredValue = CmdArgRequiredValue.Yes)]

   public String text = null;

 

    [CmdArg(ArgName = "M", RequiredArg = false,

       RequiredValue = CmdArgRequiredValue.Yes)]

   // Default to creating a new file

   public WriteMode mode = WriteMode.Create;

 

    [CmdArg(ArgName = "N", RequiredArg = false,

       RequiredValue = CmdArgRequiredValue.Yes)]

   // Default to writing the text to the files just once

   public Int32 numTimes = 1;

 

    [CmdArg(ArgName="?")]

   // Default to not show usage

   public Boolean ShowUsage = false;

   ...

Figure 3. Here's the abbreviated Options data structure, which shows how to apply the CmdArgAttribute to various fields.

 

The fields that have the CmdArgAttribute applied to them indicate fields that correspond to command-line options. When you apply a CmdArgAttribute to a field or property, you can specify three optional pieces of declarative information (see Figure 4).

 

Optional Command Argument Information

Explanation

ArgName (a String)

This is how you indicate which command-line switch string maps to the field. For example, the "/P" switch maps to the pathname field and the "/?" switch maps to the ShowUsage field. The default for ArgName is the name of the field or property itself.

RequiredArg (a Boolean)

If true, this indicates that the user must specify this switch, or else an InvalidCmdArgException exception is thrown. For example, the user must specify the "/P" and "/T" switches when running the program or the program can't run at all. The default for RequiredArg is false.

RequiredValue (an enum of Yes, No, Optional)

If Yes, this indicates that the switch requires an additional value (such as the "/P" and "/T" switches). If No (the default), it indicates that the switch cannot have an additional value after it ("/?" is an example). Setting RequiredValue to Optional indicates that this switch might or might not have a value specified with it (the downloadable sample program doesn't demonstrate this possibility).

Figure 4. Here are the three optional pieces of declarative information you can specify for a command-line argument.

 

Defining this data structure is the hard part; parsing the user's command-line arguments to set the data structure's fields is incredibly simple. Inside Main, simply do something like this:

 

static void Main(String[] args) {

   Options options = new Options();

   CmdArgParser.Parse(options, args);

 

   // Now the rest of the program can execute fine-tuning

   // its behavior by querying options's fields...

}

 

The Parse method is a static method defined in another class I created named CmdArgParser. When calling Parse, you must pass the object whose fields will be set by the Parse method. Main also passes on the command-line arguments passed to it (the args parameter) to the Parse method directly. Admittedly, this Main is pretty simple. It'd be a bit more complicated to handle some exceptions that could get thrown by the Parse method if the user enters bad command-line arguments or if the way you've defined the Options data structure is defective. The sample code shows a more robust Main that handles these scenarios better.

 

By the way, notice that the Options class sets default values for some fields. If the user doesn't specify a switch that corresponds to a field, the default value will be unaltered. I've found this to be incredibly useful when building my own utilities. Also, notice that the numTimes field is an Int32 and the mode field is a WriteMode enumerated type. These are very cool features! If possible, my parser converts the strings to the appropriate data type automatically when parsing the command-line strings. It does this conversion by checking the metadata to determine each field's type.

 

Hopefully, after all this, you can see the benefits of declarative programming and start to imagine ways you can take advantage of it yourself by defining your own custom attributes and applying them as needed. Personally, I believe declarative programming is a huge technology improvement for developers, and C# makes leveraging it easy. Using declarative programming along with object-oriented programming is certain to improve your efficiency as a developer.

 

Check out Chapter 16 of Applied Microsoft .NET Framework Programming, where you can learn what a custom attribute class is, how to define it, apply it, and look for its existence programmatically.

 

The sample code in this article is available for download.

 

Jeffrey Richter is a co-founder of Wintellect (http://Wintellect.com), a training, debugging, and consulting firm dedicated to helping companies build better software - faster. He's the author of Applied Microsoft .NET Framework Programming (Microsoft Press, http://www.amazon.com/exec/obidos/ASIN/0735614229/ ) and several Windows programming books, and he has been consulting with Microsoft's .NET Framework team since October 1999. E-mail him at mailto:[email protected].

 

 

 

 

Hide comments

Comments

  • Allowed HTML tags: <em> <strong> <blockquote> <br> <p>

Plain text

  • No HTML tags allowed.
  • Web page addresses and e-mail addresses turn into links automatically.
  • Lines and paragraphs break automatically.
Publish