8.6. Attributes

Attributes serve as metadeclarative information. C# supports several predefined (sometimes called intrinsic) attributes. In addition, the programmer can define new attribute types. These can be retrieved and queried at runtime through type reflection. Both the intrinsic and user-defined attributes are classes, although their syntax appears text based. Before we look at how we can define our own attribute types, let's briefly review the intrinsic attributes.

8.6.1. The Intrinsic Conditional Attribute

The Conditional attribute enables us to define class methods that are conditionally invoked on the basis of whether an associated string is defined. (We cannot, however, place a Conditional attribute on data members or properties.) The attribute is placed within brackets ([,]) preceding the method it modifies. For example, the methods open_debug_output() and display() are provided with the Conditional attribute:

using System.Diagnostics;
public class string_length : IComparer
{
   [Conditional( "DEBUG" )]
   private void open_debug_output()
   {
          FileStream fout  =
                 new FileStream("debug.txt", FileMode.Create );
          of = new StreamWriter( fout );
   }

   [Conditional( "DEBUG" )]
   public void display( string xs, string ys, int ret_val )
   {
          of.WriteLine("inside conditional function display()!");
          of.WriteLine("word #1: {0} : {1} ", xs, xs.Length);
          of.WriteLine("word #2: {0} : {1} ", ys, ys.Length);
          of.WriteLine("return value: {0} ",  ret_val);
   }

   // not allowed: conditional data member
   // [Conditional( "DEBUG" )]
   private StreamWriter of;
}

The string within parentheses always refers to a #define statement. For example, for the Conditional attribute to evaluate as true, we write

#define DEBUG

The use of #undef undefines a name—for example,

#undef DEBUG

Because the preprocessor commands must occur before any C# statements, we cannot nest a #undef command within our code. The methods themselves are invoked unconditionally, although they may or may not be invoked at all—for example,

public class string_length : IComparer
{
    public string_length(){
      // if DEBUG is defined, this executes
      open_debug_output();
    }
    public int Compare( object x, object y )
    {
         if (( ! ( x is string )) || ( ! ( y is string )))
           throw new ArgumentException("both must be strings");

         string xs = (string) x, ys = (string) y;
         int ret_val = 1;

         // calculate result in ret_val

         // if DEBUG is defined, this executes
         display( xs, ys, ret_val );

         return ret_val;
    }
}

If the Conditional string is not defined, invocations of the Conditional methods within our code are ignored.

8.6.2. The Intrinsic Serializable Attribute

The Serializable attribute indicates that a class can be serialized. Serialization is the persisting of an object beyond the lifetime of our executable, preserving the current state of the object for subsequent reloading. We can serialize to a particular storage device, such as a hard disk, or to a remote computer. In addition, we can specify that individual fields are to be NonSerialized. For example, here is a Matrix class defined as Serializable, but with its out_stream members marked as NonSerialized:

[ Serializable ]
class Matrix
{
    [ NonSerialized ]
    private string out_stream;

    float [,] mat;
}

Now that we've marked our class as Serializable, how do we serialize the thing? Here is an example of serializing a class object to and from a disk file—but without any error checking. This requires amazingly little work on our part.

First let's serialize the object to disk. We'll use the BinaryFormatter class defined within the nested Binary namespace. The serialization is done through BinaryFormatter's Serialize() method:

using System.Runtime.Serialization.Formatters.Binary;
using System.IO;
public void ToDisk()
{
   Stream s = new File( out_stream ).Open(FileMode.Create);
   BinaryFormatter bfm = new BinaryFormatter();
   bfm.Serialize( s, this );
   s.Close();
}

The reverse operation, restoring a serialized object from disk, is done through the Deserialize() method of BinaryFormatter:

public void FromDisk( string filename )
{
   Stream s = (new File (filename)).Open(FileMode.Open);
   BinaryFormatter bfm = new BinaryFormatter();
   Matrix m = (Matrix) bfm.Deserialize(s);
   s.Close();

   mat = m.mat;
   out_stream = filename;
}

To work for every type, Deserialize() must return an object type. This is why we must explicitly downcast it to the Matrix type.

8.6.3. The Intrinsic DllImport Attribute

The DllImport attribute allows us to invoke a method that has not been built under .NET. For example, we'd like to implement a simple start/stop Timer class to instrument the duration of selected routines.[2] The underlying routines are unmanaged methods of the Win32 API. Here is the declaration of the two functions within the Timer class:

[2] When the Counter class was removed from an earlier beta version of the .NET framework, Eric Gunnerson, author of A Programmer's Introduction to C# (Apress, 2000), kindly shared his own Counter class, which I dumbed down a bit for the simpler Timer semantics.

public class Timer
{
    private long   m_elapsedCount;
    private long   m_startCount;
    private string m_context;

    [System.Runtime.InteropServices.DllImport("KERNEL32")]
    private static extern bool
            QueryPerformanceCounter(ref long cnt);

    [System.Runtime.InteropServices.DllImport("KERNEL32")]
    private static extern bool
            QueryPerformanceFrequency(ref long frq);
}

The functions are declared extern because they are defined externally. We declare them, but of course we do not provide a definition. They can be private, public, or anything in between. We invoke them just as we would an ordinary member function—for example,

public class Timer
{
   public void start() {
          m_startCount = 0;
         QueryPerformanceCounter(ref startCount);
   }

   public void stop() {
        long stopCount = 0;
        QueryPerformanceCounter( ref stopCount );
        m_elapsedCount = ( stopCount - m_startCount );
   }

   public override string ToString()
   {
          long freq = 0;
          QueryPerformanceFrequency( ref freq );
          float seconds = m_elapsedCount
                 ? (float) m_elapsedCount / (float) freq
                 : 0.f;
          return m_context + " : " +
                 seconds.ToString() + " secs.";
   }
   // ...
}

In our WordCount program of Chapter 1, we use the Timer class as follows:

private void writeWords()
{
    Timer tt = null;[3]
    if ( m_spy )
    {
         tt = new Timer();
         tt.context = "Time to write file ";
         tt.start();
    }

    // ... the actual writing goes here ...

    if ( m_spy )
    {
         tt.stop();
         m_times.Add( tt.ToString() );
    }
}

[3] Although the program logic does not require an explicit initialization of tt, its absence causes the compiler to flag the second if statement as a use of an uninitialized object. See Section 1.8 for a discussion of the static flow analysis used by the compiler.

where m_spy is an option the user can set on the command line, m_times is an ArrayList object into which we add the timing strings of the instrumented routines, and context is a public property of the Timer class encapsulating the m_context string data member.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.221.197.95