Program Efficiently

Use declarative programming to streamline your work.

Jeffrey Richter

October 30, 2009

13 Min Read
ITPro Today logo in a gray background | ITPro Today

aspnetNOW

 

ProgramEfficiently

Usedeclarative programming to streamline your work.

 

By JeffreyRichter

 

Programmingis hard work and often can be quite tedious. I can't count the number of timesI've written a linked list (or some other algorithm) over and over again andadded just a few tweaks to the code here and there. Obviously I'm not alonebecause many new technologies have appeared over the years to help developers.I'm sure you're familiar with object-oriented programming as a technology thatallows 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 developerquestions the incredible productivity boost object-oriented programmingprovides - it has proven to be a huge benefit, especially when you design andimplement big applications.

 

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

 

GoMainstream

Anexample of declarative programming is the production of an HTML Web page. Inthis process, the "programmer" defines how the Internet browser applicationshould process the HTML tags and text to lay out the page in a window. Manyhard-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 becomemuch more mainstream - thanks to C#.

 

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

 

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

 

Inaddition, without metadata, the code to serialize the object graph would besprinkled through your application's types; there's no "serializationalgorithm," 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 YourSource-Code Files

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

 

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

 

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

 

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

 

First,your intended declarative information is in the same file (and even in the samelocation within the file) as the code to which the declarative informationapplies. This means you are far less likely to forget to modify the declarativeinformation if you make a change to the source code itself. Second, there'sonly 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 codeand the declarative information simultaneously. Finally, because the compilercompiles 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 thecompiler emits all this information into the resulting .exe or .dll file'smetadata, the code that implements the types and methods always will be in syncwith the metadata and other declarative information. You'll no longer wastecountless hours debugging because the declarative information was out of syncwith the implementation, or because you forgot to recompile the declarativeinformation, or because you used an old version of the declarative information.

 

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

 


Figure 1. This sample code listinghighlights a constructor and fields.

 

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

 

Define itand Apply it

Once youdefine a custom attribute class, you can apply an instance of it to a piece ofsource 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; }

}

Figure2. This sampleshows where you can apply custom attributes.

 

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

 

  • Applyingthe DllImport attribute to a method informs the CLR that the implementation ofthe method actually is located in unmanaged code contained in the specified.dll

  • Applyingthe Serializable attribute to a type informs the serialization formatters thatinstances of the type can be serialized to binary or SOAP

  • Applyingthe AssemblyVersion attribute to an assembly sets the version number of theassembly

  • Applyingthe ParamsArray attribute to a parameter tells compilers that the methodaccepts a variable number of arguments

  • Applyingthe WebMethod attribute to a public method tells ASP.NET that the method is anXML Web Service method callable remotely over the Internet

 

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

 

Now thatyou have a feel for declarative programming and some examples of how Microsofthas defined some custom attributes, I'd like to share with you an idea of myown.

 

In myday-to-day work, I write many small utility programs, and almost all of themrequire the end user to specify command-line arguments. This means all myapplications contain code to parse command-line attributes. One day I waspondering how I could make it easier to parse command-line arguments, and itdawned on me: declarative programming. I'll design my own CmdArgAttributecustom attribute that'll make it trivially simple for me to add command-lineargument parsing to all my programs. This article's code download defines mycustom attribute, checks for its existence, and demonstrates how to use it (seethe Download box for details). You'll probably want to refer to the source codewhile reading the remainder of this article.

 

Start it Up

Let meexplain how I (or you) would start using my custom attribute. First, define theusage for my application. The code download is a simple utility that creates afile or appends to an existing file, writing some text to the file severaltimes. The program isn't particularly interesting (or useful in real life), butit does demonstrate how to use my custom attribute to parse command-linearguments. If you run the program specifying the /? switch, this usageinformation 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 thedefault.

   /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 anderror-prone. But using my CmdArgAttribute makes it incredibly simple. Once theusage for the application is defined, the second step is to define a datastructure that contains a field for each of the application's possiblecommand-line arguments. Then, apply my CmdArgAttribute to each of the fields(see Figure 3).

 

enum WriteMode{ Create, Append }

 

// The fieldsof this class map to

// command-lineargument switches.

class Options :ICmdArgs {

   // Members identifying command-lineargument 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 filesjust once

   public Int32 numTimes = 1;

 

    [CmdArg(ArgName="?")]

   // Default to not show usage

   public Boolean ShowUsage = false;

   ...

Figure3. Here's theabbreviated Options data structure, which shows how to apply theCmdArgAttribute to various fields.

 

Thefields that have the CmdArgAttribute applied to them indicate fields thatcorrespond to command-line options. When you apply a CmdArgAttribute to a fieldor 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).

Figure4. Here are thethree optional pieces of declarative information you can specify for acommand-line argument.

 

Definingthis data structure is the hard part; parsing the user's command-line argumentsto set the data structure's fields is incredibly simple. Inside Main, simply dosomething like this:

 

static voidMain(String[] args) {

   Options options = new Options();

   CmdArgParser.Parse(options, args);

 

   // Now the rest of the program can executefine-tuning

   // its behavior by querying options'sfields...

}

 

TheParse method is a static method defined in another class I created namedCmdArgParser. When calling Parse, you must pass the object whose fields will beset by the Parse method. Main also passes on the command-line arguments passedto it (the args parameter) to the Parse method directly. Admittedly, this Mainis pretty simple. It'd be a bit more complicated to handle some exceptions thatcould get thrown by the Parse method if the user enters bad command-linearguments 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 theway, notice that the Options class sets default values for some fields. If theuser doesn't specify a switch that corresponds to a field, the default valuewill be unaltered. I've found this to be incredibly useful when building my ownutilities. Also, notice that the numTimes field is an Int32 and the mode fieldis a WriteMode enumerated type. These are very cool features! If possible, myparser converts the strings to the appropriate data type automatically whenparsing the command-line strings. It does this conversion by checking themetadata to determine each field's type.

 

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

 

Checkout Chapter16 of Applied Microsoft .NET Framework Programming, where you canlearn what a custom attribute class is, how to define it, apply it, and lookfor its existence programmatically.

 

The sample code in this article is available for download.

 

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

 

 

 

 

Read more about:

Microsoft
Sign up for the ITPro Today newsletter
Stay on top of the IT universe with commentary, news analysis, how-to's, and tips delivered to your inbox daily.

You May Also Like