Attributes

 
Chapter 4 - Advanced C# Topics
bySimon Robinsonet al.
Wrox Press 2002
  

Microsoft has defined a number of attributes in the .NET Framework base classes. For the most part, these are in some ways similar to preprocessor directives, to the extent that for the most part they don't actually translate into statements in your compiled code, but rather serve as directives to the compiler. There are also attributes that cause no compilation action, but cause extra data to be emitted to the compiled assembly we will wait till the last section), the number of attributes is theoretically unlimited, since the .NET Framework incorporates a mechanism for you to define your own custom attributes.

Essentially, an attribute is a marker that can be applied to an item in your code such as a method or class, or even an individual argument to a method, and which supplies extra information about that item. For example, the Conditional attribute can be used to mark a method as a debugging method like this:

   [Conditional("DEBUG")]     public void DoSomeDebugStuff()     {     // do something     }   

From this example, we can see that in order to apply an attribute to an item, you supply the name of the attribute in square brackets immediately before the definition of the item. Certain attributes take parameters these are supplied inside round brackets immediately following the name of the attribute.

At its most basic level, applying an attribute to an item might simply mean that the extra information about that item is left in the compiled assembly where it can be used for additional documentation purposes (using the technique of reflection, which we will discuss in the next chapter, you can also access this information programmatically from C# code). This will be the case for any custom attributes that you define. However, a number of attributes that are defined in the base classes are explicitly recognized by the C# compiler. For these particular attributes, the compiler will take certain actions, which may affect the generated code. This is the case for the Conditional attribute illustrated above, which will cause the compiler to compile the code for the Conditional item only if the named symbol has been defined using an earlier #define statement.

With such a wide variety of attributes, it is not really possible to give a systematic statement of what they do. However, we will give a flavor of what can be achieved using attributes in this section by introducing three commonly-used general-purpose attributes that are defined in the base classes, and which are recognized by the compiler. In Chapter 5, we will show you how to define your own custom attributes, and also provide examples of why you would want to do that. Also, in later chapters in the book, we will intermittently meet additional attributes from the .NET base classes.

Here we will consider the following three attributes:

  • Conditional It is possible to mark any method with the Conditional attribute. This will prevent the compiler from compiling that method or any statements that refer to it unless a named symbol has been defined. This can be used to give conditional compilation (for example, for debug builds).

  • DllImport Despite the extensive features of the .NET bass class library, there are occasions when you need to get access to the basic Windows API, or to other old-style functions that are implemented in classic Windows DLLs. The DllImport attribute exists for this purpose. It is used to mark a method as being defined in an external DLL rather than in any assembly.

  • Obsolete This attribute is used to mark a method that is now regarded as obsolete. Depending on the settings you apply to this attribute, it will cause the compiler to generate either a warning or an error if it encounters any code that attempts to use this method.

Although the details are beyond the scope of this book, we will also mention that it is possible to specify the exact layout of the fields in a struct in memory, using an attribute, StructLayout .

We will demonstrate the use of these attributes with a small example program, the Attributes sample. This sample displays a message box using the Windows API function, MessageBox , and also displays two debug messages if we are doing a debug build. One of these debug messages is called via an obsolete method.

In normal programming, you wouldn't use DllImport to display a message box, since the .NET base class method, System.Windows.Forms.MessageBox.Show() can do the job just as well. We have picked on this API function to use in our example because it is relatively well known. Usually, you would use DllImport for situations where there is no .NET class that will do the task at hand.

This is what the code for the Attributes example looks like:

   #define DEBUG   // comment out this line if doing a release build     using System;     using System.Runtime.InteropServices;     using System.Diagnostics;     namespace Wrox.ProCSharp.AdvancedCSharp     {     class MainEntryPoint     {     [DllImport("User32.dll")]     public static extern int MessageBox(int hParent, string Message,     string Caption, int Type);         static void Main()     {     DisplayRunningMessage();     DisplayDebugMessage();     MessageBox(0, "Hello", "Message", 0);     }     [Conditional("DEBUG")]     private static void DisplayRunningMessage()     {     Console.WriteLine("Starting Main routine. Current time is " +     DateTime.Now);     }     [Conditional("DEBUG")]     [Obsolete()]     private static void DisplayDebugMessage()     {     Console.WriteLine("Starting Main routine");     }     }     }   

We start off by indicating a number of additional namespaces to be used. This is because the DllImport attribute is defined in the System.Runtime.InteropServices namespace, while the Conditional attribute is defined in the System.Diagnostics namespace.

Inside the main entry class of the program, we define the method that we are going to call up from an external DLL. The MessageBox() API function is defined in the file User32.dll , so we pass the name of this file as a parameter to the DllImport attribute. Note that we also need to declare this method as extern . MessageBox() has been written in C, so the four parameters that it takes are strictly speaking defined in terms of data types available in C, but in C# terms these data types map to an int (used to store a Windows handle, which we can set to zero), two strings containing respectively the message to be displayed and the caption to be displayed in the title bar, and an integer that indicates what buttons should be displayed in the message box. Setting this final parameter to zero ensures there will be one button, entitled OK .

Usually, it would be better programming practice to use a class, System.IntPtr to store a handle, but we have stuck with int here to keep things simple.

Looking further down at the debugging functions we have defined, DisplayDebugMessage() simply writes a line to the console saying we are starting to run the program. We only want this method run if we are doing a debug build, so we mark it as conditional upon the DEBUG symbol being present. We also regard this method as being obsolete, since we have written a better version, DisplayRunningMessage() , which additionally displays the current date and time. We are trying to discourage people from using DisplayDebugMessage() any more, so we mark it with the Obsolete attribute to make sure a compiler warning gets displayed if this method gets used. There are a couple of other overloads to this attribute we could use:

   [Obsolete("The DisplayDebugMessage is obsolete. Use " +     "DisplayRunningMessage instead.")]   

This will generate a compiler warning if this method is used, but it will be the specified custom error message instead of a default one, while:

   [Obsolete("The DisplayDebugMessage is obsolete. Use " +     "DisplayRunningMessage instead.", true)]   

This will actually cause a compilation error instead of a warning if this method is used. The second, optional parameter is a bool that indicates whether the compiler should treat it as an error instead of a warning if this Obsolete item is used in your code.

Before we run the code, a quick word about the Conditional attribute. This attribute is actually quite sophisticated, since if the condition is not met, the compiler will not only not compile the code for the method, but will also automatically ignore any lines of code anywhere else in the source file that call up this method. In order for the compiler to be able to do this, the method must return a void . This makes the Conditional attribute a much neater way of arranging for a conditional compilation than the preprocessor #if...#endif directives that we saw in the last section, since these directives must be applied separately to each section of code that needs to be conditionally compiled. On the other hand, the preprocessor directives are a little more flexible because of their capacity to evaluate some logical expressions, and because they can be applied to any arbitrary section of code. The Conditional attribute can only be applied to a complete method as a unit.

Compiling and running the Attributes sample with the DEBUG symbol defined gives these results. Notice the warning given on compilation:

  csc Attributes.cs  Microsoft (R) Visual C# .NET Compiler version 7.00.9466 for Microsoft (R) .NET Framework version 1.0.3705 Copyright (C) Microsoft Corporation 2001. All rights reserved. Attributes.cs(19,10): warning CS0612:         'Wrox.ProCSharp.AdvancedCSharp.MainEntryPoint.DisplayDebugMessage()'         is obsolete Attributes Starting Main routine. Current time is 12/02/2002 18:47:32 Starting Main routine 
  


Professional C#. 2nd Edition
Performance Consulting: A Practical Guide for HR and Learning Professionals
ISBN: 1576754359
EAN: 2147483647
Year: 2002
Pages: 244

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net