for RuBoard |
Chapter 5 introduced the concept of attributes, which have already appeared in several examples. In this chapter we used the Serializable and Synchronization attributes, which are provided by .NET Framework classes. The .NET Framework makes the attribute mechanism entirely extensible, allowing you to define custom attributes, which be added to the class's metadata. This custom metadata is available through reflection and can be used at runtime. To simplify the use of custom attributes, you may declare a base class to do the work of invoking the reflection API to obtain the metadata information.
The example AttributeCustom illustrates the custom attribute InitialDirectory . InitialDirectory controls the initial current directory where the program runs. By default the current directory is the directory containing the program's executable. In the case of a Visual Studio C# project, built in Debug mode, this directory is bin\Debug , relative to the project source code directory.
Before we discuss implementing the custom attribute, let us look at how the InitialDirectory attribute is used. To be able to control the initial directory for a class, we derive the class from the base class DirectoryContext . We may then apply to the class the attribute InitialDirectory , which takes a string parameter giving a path to what the initial directory should be. The property DirectoryPath extracts the path from the metadata. If our class does not have the attribute applied, this path will be the default. Here is the code for our test program.
When you run this sample on your system, change the directory in the attribute to one that exists on your machine.
// AttributeCustom.cs using System; using System.IO; class Normal : DirectoryContext { } [InitialDirectory(@"\OI\NetCs\Chap08")] class Special : DirectoryContext { } public class AttributeCustom { public static void Main() { Normal objNormal = new Normal(); Console.WriteLine("path = {0}", objNormal.DirectoryPath ); ShowDirectoryContents( objNormal.DirectoryPath ); Special objSpecial = new Special(); Console.WriteLine("path = {0}", objSpecial.DirectoryPath ); ShowDirectoryContents( objSpecial.DirectoryPath ); } private static void ShowDirectoryContents(string path) { DirectoryInfo dir = new DirectoryInfo(path); FileInfo[] files = dir.GetFiles(); Console.WriteLine("Files:"); foreach (FileInfo f in files) Console.WriteLine(" {0}", f.Name); DirectoryInfo[] dirs = dir.GetDirectories(); Console.WriteLine("Directories:"); foreach (DirectoryInfo d in dirs) Console.WriteLine(" {0}", d.Name); } }
Here is the output:
path = C:\OI\NetCs\Chap08\AttributeCustom\bin\Debug Files: AttributeDemo.exe AttributeDemo.pdb Directories: path = c:\OI\NetCs\Chap8 Files: Directories: AppDomain Asynch AsynchThreading AttributeCustom DynamicInvocation FileIO ISerializable MarshalByReference PulseAll Reflection Remoting Serialization Threading ThreadIsolation
To create a custom attribute, you must define an attribute class, derived from the base class Attribute . By convention give your class a name ending in "Attribute." The name of your class without the "Attribute" suffix will be the name of the custom attribute. In our example the class name is InitialDirectoryAttribute , so the attribute's name is InitialDirectory .
You may provide one or more constructors for your attribute class. The constructors define how to pass positional parameters to the attribute (provide a parameter list, separated by commas). It is also possible to provide "named parameters" for a custom attribute, where the parameter information will be passed using syntax name = value .
You may also provide properties to read the parameter information. In our example, we have a property Path , which is initialized in the constructor.
// DirectoryAttribute.cs using System; public class InitialDirectoryAttribute : Attribute { private string path; public InitialDirectoryAttribute(string path) { this.path = path; } public string Path { get { return path; } } }
The last step in working with custom attributes is to provide a means to extract the custom attribute information from the metadata using the reflection classes. You can obtain the Type of any object by calling the method GetType , which is provided in the root class object . Using the class's method GetCustomAttributes you can read the custom attribute information.
To make the coding of the client program as simple as possible, it is often useful to provide a base class that does the work of reading the custom attribute information. [14] We provide a base class DirectoryContext , which is used by a class wishing to take advantage of the InitialDirectory attribute. This base class provides the property DirectoryPath to return the path information stored in the metadata. Here is the code for the base class:
[14] With single implementation inheritance there is a cost to providing a base class. If you need to derive from another class such as ContextBoundObject , the base class has to derive from that class.
// DirectoryContext.cs using System; using System.Reflection; using System.IO; public class DirectoryContext { virtual public string DirectoryPath { get { Type t = this.GetType(); foreach (Attribute a in t.GetCustomAttributes(true)) { InitialDirectoryAttribute da = a as InitialDirectoryAttribute; if (da != null) { return da.Path; } } return Directory.GetCurrentDirectory(); } } }
We must import the System.Reflection namespace because GetType returns the current Type of the object. GetCustomAttributes method can then obtain a collection of Attribute objects from the metadata. Since this collection is heterogeneous, consisting of different types, the C# as operator is used to test whether a given collection element is of the type InitialDirectoryAttribute . If we find such an element, we return the Path property. Otherwise, we return the default current directory, obtained from GetCurrentDirectory .
for RuBoard |