Compiling AttributesFortunately, from a compilation perspective, there is nothing that you need to do differently when you include attributes in your C# code. You don't need to check a check box in Visual Studio .NET or add a switch to csc.exe to have your attributes show up in the resulting assembly. For example, if you look at an assembly that contains attributes in ILDasm, you'll see something that looks like Figure 1-5.Figure 1-5. The .custom directives in an assembly10. This has nothing to do with whether or not the attribute class is sealed.
The custom attribute will be stored within a .custom directive. The location of the directive will vary depending on the target stated in the C# code. In this case, ObsoleteAttribute was used on the BadCountry class. The directive will state the full name of the attribute type, along with the constructor used in the attribute declaration.
Unfortunately, space limitations prevented us from showing the full story in Figure 1.5. There is something after the equal sign that couldn't fit in the image. Listing 1-7 shows what ends up on the other side.Listing 1-7. The Full Attribute Format in an Assembly.class public auto ansi beforefieldinit BadCountryextends [mscorlib]System.Object{.custom instance void[mscorlib]System.ObsoleteAttribute::.ctor(string,bool) =( 01 00 40 54 68 69 73 20 63 6C 61 73 73 20 73 68 // ..@This class sh6F 75 6C 64 20 6E 6F 20 6C 6F 6E 67 65 72 20 62 // ould no longer b65 20 75 73 65 64 20 2D 20 73 77 69 74 63 68 20 // e used - switch74 6F 20 49 6D 70 72 6F 76 65 64 43 6F 75 6E 74 // to ImprovedCount72 79 2E 01 00 00 ) // ry....} // end of class BadCountry
You can glean some general ideas as to what the bytes represent, but you do not need to know the full details of the format to use and create attributes successfully. As you'll see in Chapter 4, the API to read metadata from an assembly is rather straightforward and does not require you to know the layout of the byte array.NOTE For those who are curious to know what the bytes stand for, Appendix of this book goes through the format in excruciating detail. Most attributes will end up in a .custom directive. Interestingly enough, they are known as custom attributes. However, there is a special set of attributes that, when present in code, will end up in other places in the assembly. These are known as pseudo-custom attributes.11 You declare them in the same way that you declare any other attribute in your C# code, but the end results in the assembly are vastly different. For example, take a look at the following code:[assembly: AssemblyVersion("1.0.0.0")]
When you compile your code, you won't find a .custom directive with a type name that contains AssemblyVersionAttribute. Figure 1-6 shows what happens when you use AssemblyVersionAttribute.Figure 1-6. Pseudo-custom attributes in assembliesIn this case, the .ver directive contains the version information of the assembly.There is a reason why pseudo-custom attributes are stored in this fashion: it's more efficient. Pseudo-custom attributes don't have the overhead of storing a bunch of bytes that a normal custom attribute requires. An explicit example of this is when you make a class serializable, like this:[type: Serializable]public class SerializableCountry{ // ...}The ILDasm results are as follows:.class public auto ansi serializable beforefieldinit SerializableCountryextends [mscorlib]System.Object{} // end of class SerializableCountry
Notice that the serializable attribute now ends up in the class definition. There is no .custom directive anywhere on the class. What is really going on is that a bit flag is being set for SerializableCountry. When you use the SerializableAttribute attribute with the class, here's what the token looks like:TypDefName: Apress.NetAttributes.SerializableCountry (02000008)Flags : [Public] [AutoLayout] [Class] [Serializable] [AnsiClass] (00102001)Without the SerializableAttribute attribute, you'll notice that a value in theFlags field changes:TypDefName: Apress.NetAttributes.SerializableCountry (02000008)Flags : [Public] [AutoLayout] [Class] [AnsiClass] (00100001)Pseudo-custom attributes have this special storage consideration because they are used extensively by compilers or by the CLR. It's much faster for the CLR to look at a bit field to see if it's serializable, rather than to go through all of the custom attributes (if any exist) to see if one of them is of the SerializableAttribute type. Of course, with this efficiency comes a trade-off in verbosity. A class is either serializable or it's not serializable. Contrast that with the ObsoleteAttribute, which lets you give a client a helpful message and make obsolete elements cause either a warning or an error during compilation.SOURCE CODE The code for these examples is in Chapter1\Country and Chapter1\CountryClient.ConclusionIn this chapter, you learned the following about attributes: