Built-In Attributes


You saw in previous sections that the .NET Framework includes a number of attributes, such as DebuggableAttribute and AssemblyTitleAttribute. This section discusses some of the more common attributes defined in the .NET Framework, and when you might want to use them.

The attributes covered in this section are:

  • System.Diagnostics.ConditionalAttribute

  • System.ObsoleteAttribute

  • System.SerializableAttribute

  • System.Reflection.AssemblyDelaySignAttribute

There is more information about the other attributes that ship with the .NET Framework in the .NET Framework SDK documentation.

Another extremely useful tool when working with .NET is a program called Reflector and is downloadable from www.aisto.com/roeder/dotnet. This tool uses reflection to inspect assemblies. You can use it to find, with a few mouse clicks, all classes that derive from System.Attribute. It's one tool you shouldn't be without.

System.Diagnostics.ConditionalAttribute

This is one of the most useful attributes of all, because it permits sections of code to be included or excluded based on the definition of a symbol at compilation time. This attribute is contained within the System.Diagnostics namespace, which includes classes for debug and trace output, event logging, performance counters, and process information. The following code is an example of how to use this attribute:

 using System; using System.Diagnostics; namespace TestConditional { class Program { static void Main(string[] args) { // Call a method only available if DEBUG is defined... Program.DebugOnly(); } // This method is atributed and will ONLY be included in // the emitted code if the DEBUG symbol is defined when // the program is compiled. [Conditional("DEBUG")] public static void DebugOnly() { // This line will only be displayed in debug builds... Console.WriteLine("This string only displays in Debug"); } } } 

The source code for this example is available in the Chapter27/Conditional directory. The code calls the static DebugOnly() method, which is attributed with the Conditional attribute. This function just displays a line of text on the console.

When a C# source file is compiled, you can define symbols on the command line. the Conditional attribute will prevent calls to a method that is conditional on a symbol that is not present.

The DEBUG symbol will be set automatically for you if you compile a Debug build within Visual Studio 2005. If you want to define or refine the symbols for a particular project then display the project Properties dialog and navigate to the Build option of Configuration Properties, as shown in Figure 27-4.

image from book
Figure 27-4

Notice that the defaults for a Debug build are to check both the DEBUG and TRACE options.

To define a symbol on the command line, you use the /d: switch (the short form for /define: — you can type the entire string if you wish):

>csc /d:DEBUG conditional.cs

If you compile and run the file with the command line shown, you'll see the output string This string only displays in Debug. If you compile the code without defining the DEBUG symbol on the command line, then the program will display nothing. Note that the options for csc are case-sensitive.

To get a clearer picture of what is happening within the generated code, use Ildasm to view the generated code, as shown in Figure 27-5.

image from book
Figure 27-5

When the DEBUG symbol is not defined, the IL generated for the Main() method is:

.method private hidebysig static void  Main(string[] args) cil managed {   .entrypoint   // Code size       1 (0x1)   .maxstack  8   IL_0000:  ret } // end of method Program::Main 

This code simply returns from the Main method.

If, however, you compile the file with the /d:DEBUG switch, you'll see the following code:

.method private hidebysig static void  Main(string[] args) cil managed {   .entrypoint   // Code size       8 (0x8)   .maxstack  8   IL_0000:  nop IL_0001:  call       void TestConditional.Program::DebugOnly()   IL_0006:  nop   IL_0007:  ret } // end of method Program::Main

The line highlighted is the call to the conditionally compiled method. Use of Conditional() will remove calls to a method, but not the method itself.

Important

The Conditional attribute can be used only on methods that return void — otherwise removing the call would mean that no value was returned; however, you can attribute a method that has out or ref parameters — the variables will retain their original value.

System.ObsoleteAttribute

This attribute shows the attention to detail that the Microsoft engineers have put into the .NET Framework. The the Obsolete attribute can be used to mark a class, method, or any other entry in an assembly as being no longer used.

This attribute would be useful, for example, when publishing a library of classes. It is inevitable that through the course of developing a set of classes, some of those classes/methods/properties will be superseded. This attribute can be used to prepare the users of your code for the eventual withdrawal of a particular feature.

Suppose that in version one of your application, you have a class like this:

 public class Coder { public Coder () { } public void CodeInCPlusPlus () { } } 

You compile and use this class for several years, but then something new comes along to replace the old functionality:

 public void CodeInCSharp () { } 

Naturally, you want to allow the users of your library to use CodeInCPlusPlus() for some time, but you would also like to alert them to the fact that there is a newer method by displaying a warning message at compile time, informing your users of the existence of CodeInCSharp(). To do this, all you need to add is the Obsolete attribute, as shown here:

 [Obsolete("CodeInCSharp instead.")] public void CodeInCPlusPlus () { }

When you compile again, for Debug or Release, you'll receive a warning from the C# compiler that you are using an obsolete (or soon to be obsolete) method:

Obsolete.cs(20,1): warning CS0618: 'Developer.CodeInCPlusPlus()' is obsolete:         'CodeInCSharp instead.'

Over the course of time, everyone will become tired of seeing this warning message each time the code is compiled, so eventually everyone (well, almost everyone) will utilize CodeInCSharp(). Eventually, you'll want to entirely drop support for CodeInCPlusPlus(), so you add an extra parameter to the Obsolete attribute:

 [Obsolete("You must CodeInCSharp instead.", true)] public void CodeInCPlusPlus() { }

When a user attempts to compile this method, the compiler will generate an error and halt the compilation with the following message:

Obsolete.cs(20,1): error CS0619: 'Developer.CodeInCPlusPlus()' is obsolete:       'You must CodeInCSharp instead.'

Using this attribute provides users of your class with help in modifying applications that use your class, as the class evolves.

For binary classes, such as components that you purchase without source code, this isn't a good way to do versioning — the .NET Framework has excellent built-in versioning capabilities that you looked at in Chapter 26. But the Obsolete attribute does provide a useful way to hint that a particular feature of your classes should no longer be used.

System.SerializableAttribute

Serialization is the name for storing and retrieving an object, either in a disk file, memory, or anywhere else you can think of. When serialized, all instance data is persisted to the storage medium, and when deserialized, the object is reconstructed and is indistinguishable from its original instance.

For any of you who have programmed in MFC, ATL, or VB before, and had to worry about storing and retrieving instance data, this attribute will save you a great deal of typing. Suppose that you have a C# object instance that you would like to store in a file, such as:

 public class Person { public Person () { } public int Age; public int WeightInPounds; } 

In C# (and indeed any of the languages built on the .NET Framework), you can serialize an instance's members without writing any code — well almost. All you need to do is add the the Serializable attribute to the class, and the .NET runtime will do the rest for you.

When the runtime receives a request to serialize an object, it checks if the object's class implements the ISerializable interface, and if it does not then the runtime checks if the class is attributed with the Serializable attribute. I won't discuss ISerializable any further here — it is an advanced topic.

If the Serializable attribute is found on the class, then .NET uses reflection to get all instance data — whether public, protected, or private — and to store this as the representation of the object. Deserialization is the opposite process — data is read from the storage medium, and this data is assigned to instance variables of the class.

The following code shows a class marked with the Serializable attribute:

 [Serializable] public class Person {    public Person ()    {    }        public int Age;    public int WeightInPounds; }

The entire code for this example is available in the Chapter27/Serialize subdirectory. To store an instance of this Person class, use a Formatter object — which converts the data stored within your class into a stream of bytes. The system comes with two default formatters, BinaryFormatter and SoapFormatter (these have their own namespaces below System.Runtime.Serialization .Formatters). The following code shows how to use the BinaryFormatter to store a person object:

 using System; using System.Runtime.Serialization.Formatters.Binary; using System.IO; public static void Serialize () { // Construct a person object. Person  me = new Person (); // Set the data that is to be serialized. me.Age = 38; me.WeightInPounds = 200; // Create a disk file to store the object to... Stream  s = File.Open ("Me.dat" , FileMode.Create); // And use a BinaryFormatted to write the object to the stream... BinaryFormatter bf = new BinaryFormatter (); // Serialize the object. bf.Serialize (s , me); // And close the stream. s.Close (); } 

The code first creates the person object and sets the Age and WeightInPounds data, and then it constructs a stream on a file called Me.dat. The binary formatter utilizes the stream to store the instance of the person class into Me.dat, and the stream is closed.

The default serialization code will store all the public contents of the object, which in most cases is what you would want. But under some circumstances you may wish to define one or more fields that should not be serialized. That's easy too:

[Serializable] public class Person {    public Person ()    {    }        public int Age;     [NonSerialized]    public int WeightInPounds; }

When this class is serialized, only the Age member will be stored — the WeightInPounds member will not be persisted and so will not be retrieved on deserialization.

Deserialization is basically the opposite of the preceding serialization code. The example that follows opens a stream on the Me.dat file created earlier, constructs a BinaryFormatter to read the object, and calls its Deserialize method to retrieve the object. It then casts this into a Person and writes the age and weight to the console:

 public static void DeSerialize () { // Open the file this time. Stream  s = File.Open ("Me.dat" , FileMode.Open); // And use BinaryFormatted to read object(s) from the stream. BinaryFormatter bf = new BinaryFormatter (); // Deserialize the object. object  o = bf.Deserialize (s); // Ensure it is of the correct type... Person  p = o as Person; if (p != null) Console.WriteLine ("DeSerialized Person aged: {0} weight: {1}" ,  p.Age , p.WeightInPounds); // And close the stream. s.Close (); } 

You can use the NonSerialized attribute to mark data that does not need to be serialized, such as data that can be recomputed or calculated when necessary. An example is a class that computes prime numbers — you may well cache primes to speed up response times while using the class; however, serializing and deserializing a list of primes is unnecessary because they can simply be recomputed on request. At other times, the member may be relevant only to that specific use of the object. For example, in an object representing a word processor document, you would want to serialize the content of the document but usually not the position of the insertion point — when the document next loads you simply place the insertion point at the start of the document.

If you want yet more control over how an object is serialized, you can implement the ISerializable interface. This is an advanced topic, and I won't take this discussion any further in this book.

System.Reflection.AssemblyDelaySignAttribute

The System.Reflection namespace provides a number of attributes, some of which have been shown earlier in the chapter. One of the more complex to use is AssemblyDelaySign. In Chapter 26, you learned about building assemblies, creating shared assemblies, and registering them in the Global Assembly Cache (GAC). the .NET Framework also permits you to delay-sign an assembly, which means that you can register it in the GAC for testing without a private key.

One scenario in which you might use delayed signing is when developing commercial software. Each assembly that is developed in-house needs to be signed with your company's private key before being shipped to your customers. So, when you compile your assembly you reference the key file before you can register the assembly in the GAC.

However, many organizations would not want their private key to be on every developer's machine. For this reason, the runtime enables you to partially sign the assembly and tweak a few settings so that your assembly can be registered within the GAC. When fully tested, it can be signed by whoever holds the private key file. This may be your QA department, one or more trusted individuals, or the marketing department.

The following Try It Out shows how you can delay-sign a typical assembly, register it in the GAC for testing, and finally complete the signing by adding in the private key.

Try It Out – Extracting the Public Key

image from book
  1. First, you need to create a key file with the sn.exe utility. The key file will contain the public and private keys, so call it Company.Key:

    >sn –k Company.Key
  2. Then you need to extract the public key portion for use by developers with the –p option:

    >sn –p Company.Key Company.Public

    This command will produce a key file Company.Public with only the public part of the key. This public key file can be copied onto all machines and doesn't need to be kept safe — it's the private key file that needs to be secure. Store the Company.Key file somewhere safe, because it only needs to be used when you wish to finally sign your assemblies.

  3. In order to delay-sign an assembly and register that assembly within the GAC, you also need to obtain the public key token — this is basically a shorter version of the public key, used when registering assemblies. You can obtain the token in one of two ways:

    • From the public key file itself:

      >sn –t Company.Public
    • From any assembly signed with the key:

      >sn –T <assembly>
image from book

Both of these methods will display a hashed version of the public key and are case-sensitive. I'll explain this further when you register the assembly.

Delay-Signing the Assembly

The following code shows how to attribute an assembly for delayed signing. It is available in the Chapter27/DelaySign directory:

 using System; using System.Reflection; // Define the file that contains the public key.  [assembly: AssemblyKeyFile ("Company.Public") ] // And that this assembly is to be delay signed.  [assembly: AssemblyDelaySign (true) ]  public class DelayedSigning { public DelayedSigning () { } } 

The AssemblyKeyFile attribute defines the file where the key is to be found. This can be either the public key file, or for more trusted individuals, the file containing both public and private keys. the AssemblyDelaySign attribute defines whether the assembly will be fully signed (false) or delay- signed (true). Note that you can also set the key file and delay sign option from the user interface in Visual Studio 2005, as shown in Figure 27-6 — this is a portion of the Project Properties window.

image from book
Figure 27-6

As an alternative to setting this from the user interface, you can also alter the AssemblyInfo.cs file and define the AssemblyDelaySign and assemblyKeyFile attributes there.

When compiled, the assembly will contain an entry in the manifest for the public key. In fact, the manifest will also contain enough room for the private key too, so re-signing the assembly will not change it in any way (other than writing a few extra bytes into the manifest). If you try to run the assembly you will receive the error shown in Figure 27-7.

image from book
Figure 27-7

This is so because, by default, the .NET runtime will only load unsigned assemblies (where no key file has been defined) or run fully signed assemblies that contain both the public and private key pairs. The section that follows on registering an assembly in the GAC also shows how to load partially signed assemblies using a process called skip verification.

Registering in the GAC

Attempting to use the Gacutil tool (which you met in Chapter 26) to register a delay-signed assembly in the GAC will generate an error message similar to this:

Microsoft (R) .NET Global Assembly Cache Utility. Version 2.0.50214.0 Copyright (C) Microsoft Corporation. All rights reserved.     Failure adding assembly to the cache: Strong name signature could not be verified. Was the assembly built delay-signed? 

The assembly is only partially signed at the moment, and by default the GAC and Visual Studio .NET will only accept assemblies with a complete strong name. You can, however, instruct the GAC to skip verification of the strong name on a delay signed assembly by using the sn utility. Remember the public key token from earlier? This is where it comes into play.

>sn -Vr *,34AAA4146EE01E4A

This instructs the GAC to permit any assemblies with a public key token of 34AAA4146EE01E4A4A to be registered. Typing this at the command prompt will generate the following message:

Microsoft (R) .NET Framework Strong Name Utility Version 2.0.50214.0 Copyright (C) Microsoft Corporation. All rights reserved.     Verification entry added for assembly '*,34AAA4146EE01E4A4A'

Attempting to install the assembly into the GAC with Gacutil will now succeed. You don't need to use the public key value when adding a verification entry for your assembly — you can specify that all assemblies can be registered by using:

>sn –Vr *

Or you can specify the assembly by typing its full name, like this:

>sn –Vr DelaySign.dll

This data is permanently held in what is called the verification skip table, which is a file stored on disk. To obtain a list of the entries in the verification skip table, type the following (these commands are case-sensitive):

>sn -Vl

This is the output on my machine:

Microsoft (R) .NET Framework Strong Name Utility   Version 2.0.50214.0 Copyright (C) Microsoft Corporation. All rights reserved.     Assembly/Strong Name                  Users =========================================== *,03689116d3a4ae33                    All users *,33aea4d316916803                    All users *,34AAA4146EE01E4A                    All users *,631223CD18E5C371                    All users *,b03f5f7f11d50a3a                    All users *,b77a5c561934e089                    All users

Notice the Users column — you can define that a given assembly can be loaded into the GAC by a subset of all users. Check out the sn.exe documentation for further details of this and other assembly naming options.

Completing the Strong Name

The last stage in the process is to compile the public and private keys into the assembly — an assembly with both entries is said to be strongly named and can be registered in the GAC without a skip verification entry.

Once again, use the sn.exe utility, this time with the –R switch. the –R switch means that you want to re-sign the assembly and add in the private key portion:

>sn -R delaysign.dll Company.Key

This will display the following:

Microsoft (R) .NET Framework Strong Name Utility Version 2.0.50214.0 Copyright (C) Microsoft Corporation. All rights reserved.     Assembly 'delaysign.dll' successfully re-signed

The other parameters along with the –R switch are the name of the assembly to be re-signed and the key file that contains the public and private keys.




Beginning Visual C# 2005
Beginning Visual C#supAND#174;/sup 2005
ISBN: B000N7ETVG
EAN: N/A
Year: 2005
Pages: 278

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