![]() CATEGORIES: BiologyChemistryConstructionCultureEcologyEconomyElectronicsFinanceGeographyHistoryInformaticsLawMathematicsMechanicsMedicineOtherPedagogyPhilosophyPhysicsPolicyPsychologySociologySportTourism |
Nbsp; Detecting the Use of a Custom AttributeDefining an attribute class is useless by itself. Sure, you could define attribute classes all you want and apply instances of them all you want, but this would just cause additional metadata to be written out to the assembly—the behavior of your application code wouldn’t change. In Chapter 15, “Enumerated Types and Bit Flags,” you saw that applying the Flags attribute to an enumerated type altered the behavior of System.Enum’s ToString and Format methods. The reason that these methods behave differently is that they check at run time if the enumerated type that they’re operating on has the Flags attribute metadata associated with it. Code can look for the presence of attributes by using a technology called reflection. I’ll give some brief demonstrations of reflection here, but I’ll discuss it fully in Chapter 23, “Assembly Loading and Reflection.” If you were the Microsoft employee responsible for implementing Enum’s Format method, you would implement it like the following.
public override String ToString() {
// Does the enumerated type have an instance of // the FlagsAttribute type applied to it? if (this.GetType().IsDefined(typeof(FlagsAttribute), false)) { // Yes; execute code treating value as a bit flag enumerated type. ... } else { // No; execute code treating value as a normal enumerated type. ... } ... }
This code calls Type’s IsDefined method, effectively asking the system to look up the metadata for the enumerated type and see whether an instance of the FlagsAttribute class is associated with it. If IsDefined returns true, an instance of FlagsAttribute is associated with the enumer- ated type, and the Format method knows to treat the value as though it contained a set of bit flags. If IsDefined returns false, Format treats the value as a normal enumerated type. So if you define your own attribute classes, you must also implement some code that checks for the existence of an instance of your attribute class (on some target) and then execute some alternate code path. This is what makes custom attributes so useful! The FCL offers many ways to check for the existence of an attribute. If you’re checking for the existence of an attribute via a System.Type object, you can use the IsDefined method as shown earlier. However, sometimes you want to check for an attribute on a target other than a type, such as an assembly, a module, or a method. For this discussion, let’s concentrate on the extension meth- ods defined by the System.Reflection.CustomAttributeExtensions class. This class defines three static methods for retrieving the attributes associated with a target: IsDefined, GetCustom Attributes, and GetCustomAttribute. Each of these functions has several overloaded versions. For example, each method has a version that works on type members (classes, structs, enums, interfaces, delegates, constructors, methods, properties, fields, events, and return types), parame- ters, and assemblies. There are also versions that allow you to tell the system to walk up the deriva- tion hierarchy to include inherited attributes in the results. Table 18-1 briefly describes what each method does.
TABLE 18-1System.Reflection.CustomAttributeExtensions’ Methods That Reflect over Metadata Looking for Instances of CLS-Compliant Custom Attributes
If you just want to see if an attribute has been applied to a target, you should call IsDefined because it’s more efficient than the other two methods. However, you know that when an attribute is applied to a target, you can specify parameters to the attribute’s constructor and optionally set fields and properties. Using IsDefined won’t construct an attribute object, call its constructor, or set its fields and properties. If you want to construct an attribute object, you must call either GetCustomAttributes or GetCustomAttribute. Every time one of these methods is called, it constructs new instances of the specified attribute type and sets each of the instance’s fields and properties based on the values specified in the source code. These methods return references to fully constructed instances of the applied attribute classes. When you call any of these methods, internally, they must scan the managed module’s metadata, performing string comparisons to locate the specified custom attribute class. Obviously, these opera- tions take time. If you’re performance conscious, you should consider caching the result of calling these methods rather than calling them repeatedly asking for the same information. The System.Reflection namespace defines several classes that allow you to examine the con- tents of a module’s metadata: Assembly, Module, ParameterInfo, MemberInfo, Type, MethodInfo, ConstructorInfo, FieldInfo, EventInfo, PropertyInfo, and their respective *Builder classes. All of these classes also offer IsDefined and GetCustomAttributes methods. The version of GetCustomAttributes defined by the reflection classes returns an array of Object instances (Object[]) instead of an array of Attribute instances (Attribute[]). This is because the reflection classes are able to return objects of non–CLS-compliant attribute classes. You shouldn’t be concerned about this inconsistency because non–CLS-compliant attributes are incredibly rare. In fact, in all of the time I’ve been working with the .NET Framework, I’ve never even seen one.
There’s one more thing you should be aware of: When you pass a class to IsDefined, GetCustom Attribute, or GetCustomAttributes, these methods search for the application of the attribute class you specify or any attribute class derived from the specified class. If your code is looking for a specific attribute class, you should perform an additional check on the returned value to ensure that what these methods returned is the exact class you’re looking for. You might also want to consider defining your attribute class to be sealed to reduce potential confusion and eliminate this extra check. Here’s some sample code that lists all of the methods defined within a type and displays the attri- butes applied to each method. The code is for demonstration purposes; normally, you wouldn’t apply these particular custom attributes to these targets as I’ve done here.
using System; using System.Diagnostics; using System.Reflection;
[assembly: CLSCompliant(true)]
[Serializable] [DefaultMemberAttribute("Main")] [DebuggerDisplayAttribute("Richter", Name = "Jeff", Target = typeof(Program))] public sealed class Program { [Conditional("Debug")] [Conditional("Release")] public void DoSomething() { }
public Program() { }
[CLSCompliant(true)] [STAThread] public static void Main() { // Show the set of attributes applied to this type ShowAttributes(typeof(Program)); // Get the set of methods associated with the type var members = from m in typeof(Program).GetTypeInfo().DeclaredMembers.OfType<MethodBase>() where m.IsPublic select m;
foreach (MemberInfo member in members) { // Show the set of attributes applied to this member ShowAttributes(member); } }
private static void ShowAttributes(MemberInfo attributeTarget) { var attributes = attributeTarget.GetCustomAttributes<Attribute>();
Console.WriteLine("Attributes applied to {0}: {1}", attributeTarget.Name, (attributes.Count() == 0 ? "None" : String.Empty));
foreach (Attribute attribute in attributes) { // Display the type of each applied attribute Console.WriteLine(" {0}", attribute.GetType().ToString());
if (attribute is DefaultMemberAttribute) Console.WriteLine(" MemberName={0}", ((DefaultMemberAttribute) attribute).MemberName);
if (attribute is ConditionalAttribute) Console.WriteLine(" ConditionString={0}", ((ConditionalAttribute) attribute).ConditionString);
if (attribute is CLSCompliantAttribute) Console.WriteLine(" IsCompliant={0}", ((CLSCompliantAttribute) attribute).IsCompliant);
DebuggerDisplayAttribute dda = attribute as DebuggerDisplayAttribute; if (dda != null) { Console.WriteLine(" Value={0}, Name={1}, Target={2}", dda.Value, dda.Name, dda.Target); } } Console.WriteLine(); } }
Building and running this application yields the following output.
Attributes applied to Program: System.SerializableAttribute System.Diagnostics.DebuggerDisplayAttribute Value=Richter, Name=Jeff, Target=Program System.Reflection.DefaultMemberAttribute MemberName=Main Attributes applied to DoSomething: System.Diagnostics.ConditionalAttribute ConditionString=Release System.Diagnostics.ConditionalAttribute ConditionString=Debug
Attributes applied to Main: System.CLSCompliantAttribute IsCompliant=True System.STAThreadAttribute
Attributes applied to .ctor: None
Date: 2016-03-03; view: 683
|