Reflection and Generics


In .NET Framework 2.0, Reflection is extended to accommodate open and closed generic types and methods. Predictably, the Type class is the focal point of changes to accommodate generic types. For generic methods, MethodInfo has been enhanced to reflect generic methods. (Generics were introduced in Chapter 6.) Open types are generic types with unbound type parameters, whereas closed types have bound type parameters. With Reflection, you can browse bound and unbound parameters, create instances of generic types, and invoke generic methods at run time.

IsGeneric and IsGenericTypeDefinition

With Reflection, you can query the state of a generic type of method. Is this a generic type or method? If confirmed, is the generic type or method open or closed? Type.IsGeneric is a Boolean property that confirms the presence of a generic type; Type.IsGenericTypeDefinition, another Boolean property, indicates whether the generic is open or closed. For methods, the MethodInfo.IsGenericMethod property confirms that a method is a generic method, whereas the MethodInfo.IsGenericTypeDefinition property indicates whether the generic method is open or closed. This program demonstrates the four properties:

 using System; using System.Reflection; public class ZClass<T, V> {    public T membera; } public class XClass {     public void MethodA<T>() {     } } namespace Donis.CSharpBook{     class Starter{         static void Main(){             Type [] types={typeof(ZClass<,>), typeof(ZClass<int,int>)};             bool [,] bresp= { {types[0].IsGenericType,                                types[0].IsGenericTypeDefinition},                               {types[1].IsGenericType,                                types[1].IsGenericTypeDefinition}};             Console.WriteLine("Is ZClass<,> a generic type? "+bresp[0,0]);             Console.WriteLine("Is ZClass<,> open? "                 +bresp[0,1]);             Console.WriteLine("Is ZClass<int,int> a generic type? "                 +bresp[1,0]);             Console.WriteLine("Is ZClass<int,int> open? "+bresp[1,1]);             Type tObj=typeof(XClass);             MethodInfo method=tObj.GetMethod("MethodA");             bool [] bMethod={method.IsGenericMethod,                              method.IsGenericMethodDefinition};             Console.WriteLine("Is XClass.MethodA<T> a generic method? "                 +bMethod[0]);             Console.WriteLine("Is XClass.MethodA<T> open? "+bMethod[1]);         }     } } 

typeof

The typeof operator is used in the preceding code to extract the Type object of an open or constructed type. The Type object has a single parameter, which is a string and identifies the generic type. Connote an open type using empty parameters. For example, ZClass<T> would be ZClass<>. Multiple generic type parameters are indicated with n-1 commas. For the typeof operator, ZClass<,> is compatible with ZClass<K,V>. Indicate a closed type by including the actual parameter types, such as ZClass<int, int>. In addition to the typeof operator, Type.GetType and Type.GetGenericTypeDefinition methods return Type objects representing generic types.

GetType

Type.GetType is available in two flavors: an instance and a static method. Type.GetType is an instance method and returns the open type used to construct the object. The method has no parameters. The Type.GetType static method is overloaded to return a Type object for either an open or a closed type. The pivotal parameter of the static GetType method is a string naming the generic type. To specify an open type, the string is the name of the generic with the number of parameters affixed. The suffix is preceded with a "`". For example, "NamespaceA .XClass`2" would represent XClass<K,V>. XClass has two type parameters. For a closed type, you need to add the bound type parameters. After the number of type parameters, list the bound type parameters. The bound type parameters are contained in square brackets.

"NamespaceB.ZClass`3[System.Int32, System.Int32, System.Decimal]" identifies ZClass <int, int, decimal>. This is the general format:

 Open type: GenericType`NumberofParameters Closed Type: GenericType`NumofParameters[parameter list] 

The following sample code demonstrates both the instance and static GetType methods:

 using System; namespace Donis.CSharpBook {     class ZClass<K,V> {         public void FunctionA(K argk, V argv) {         }     }     class XClass<T> {         public void FunctionB(T argt) {         }     }     class Starter {         public static void Main() {             ZClass<int, decimal> obj=new ZClass<int, decimal>();             Type typeClosed=obj.GetType();             Console.WriteLine(typeClosed.ToString());             Type typeOpen=Type.GetType("Donis.CSharpBook.XClass`1");             Console.WriteLine(typeOpen.ToString());             Type typeClosed2=Type.GetType(                 "Donis.CSharpBook.ZClass`2[System.Int32, System.Decimal]");             Console.WriteLine(typeClosed2.ToString());         }     } } 

GetGenericTypeDefinition

Closed types are constructed from open types. Type.GetGenericTypeDefinition returns the Type object of the open type used to construct a closed type.

This is the Type.GetGenericTypeDefinition syntax:

 Type GetGenericTypeDefinition() 

The following code highlights the GetGenericTypeDefinition method:

 using System; namespace Donis.CSharpBook {     class ZClass<K,V> {         public void FunctionA(K argk, V argv) {         }     }     class Starter {         public static void Main() {             ZClass<int, decimal> obj=new ZClass<int, decimal>();             ZClass<string, float> obj2=new ZClass<string, float>();             Type closedType=obj.GetType();             Type openType=closedType.GetGenericTypeDefinition();             Type closedType2=obj2.GetType();             Type openType2=closedType2.GetGenericTypeDefinition();             Console.WriteLine(openType.ToString());             Console.WriteLine(openType2.ToString());         }     } } 

The preceding code displays identical strings. Why? The open type of ZClass<int, decimal> and ZClass<string, float> is the same, which is ZClass<K, V>.

Type.GetMethod and MethodInfo.GetGenericMethodDefinition are comparable to Type.GetType and Type.GetGenericTypeDefinition, respectively, but pertain to generic methods.

GetGenericArguments

You can now extract a Type object for a generic type and a MethodInfo object for a generic method. Determining the number of unbound or bound parameters is a natural next step. If bound, the type of each parameter would be useful. GetGenericArguments is the universal method for enumerating parameters of generic type or method. GetGenericArguments enumerates unbound and bound parameters.

This is the Type.GetGenericArguments syntax:

 Type [] GetGenericArguments() MethodInfo.GetGenericArguments syntax: Type [] GetGenericArguments() 

The following code demonstrates both Type.GetGenericArguments and MethodInfo.GetGenericArguments:

 using System; namespace Donis.CSharpBook {     class ZClass<K,V> {         public void FunctionA(K argk, V argv) {         }     }     class Starter {         public static void Main() {             ZClass<int, decimal> obj=new ZClass<int, decimal>();             ZClass<string, float> obj2=new ZClass<string, float>();             Type closedType=obj.GetType();             Type openType=closedType.GetGenericTypeDefinition();             Type closedType2=obj2.GetType();             Type openType2=closedType2.GetGenericTypeDefinition();             Console.WriteLine(openType.ToString());             Console.WriteLine(openType2.ToString());         }     } } 

Creating Generic Types

Generic types can be created at run time using Reflection. First, extract the open type of the generic. This chapter has shown several ways to accomplish this, including using GetType and GetGenericTypeDefinition. Next, bind the type arguments of the open type. The result will be a bound (closed) type. Finally, create an instance of the bound type in the customary manner. (Activator.CreateInstance works well.)

The Type.MakeGenericType method binds type parameters to an open type of a generic. MakeGenericType has a single parameter, which is an array of Type objects. Each element of the array matches a specific type to a generic parameter. If the generic has three parameters, the array passed to MakeGenericType will have three elements—each representing a bound parameter.

This is the prototype of the MakeGenericType method:

 Type.MakeGenericType syntax: void MakeGenericType(Type [] genericArguments) 

Generic methods, like nongeneric methods, can be invoked dynamically at run time. MethodInfo.MakeGenericType binds parameters to generic types, similar to Type.MakeGenericType. The benefits and pitfalls of dynamic invocation are similar to nongeneric methods. After binding parameters to a generic method, the method can be invoked using Reflection. The following code creates a generic type and then invokes a generic method at run time:

 using System; using System.Reflection; namespace Donis.CSharpBook{     public class GenericZ <K, V, Z>         where K: new()         where V: new() {         public void MethodA<A>(A argument1, Z argument2) {             Console.WriteLine("MethodA invoked");         }         private K field1=new K();         private V field2=new V();     }     class Starter{         static void Main(){             Type genericType=typeof(GenericZ<,,>);             Type [] parameters={typeof(int),typeof(float),                 typeof(int)};             Type closedType=genericType.MakeGenericType(parameters);             MethodInfo openMethod=closedType.GetMethod("MethodA");             object newObject = Activator.CreateInstance(closedType);             parameters=new Type [] {typeof(int)};             MethodInfo closedMethod=                 openMethod.MakeGenericMethod(parameters);             object[] methodargs={2, 10};             closedMethod.Invoke(newObject, methodargs);         }     } } 

Reflection Security

Some reflection operations, such as accessing protected and private members, require ReflectionPermission security permission. ReflectionPermission is typically granted to local and intranet applications, but not to Internet applications. Set the appropriate ReflectionPermission flag to grant or deny specific reflection operations.

Table 10-9 lists the ReflectionPermission flags.

Table 10-9: ReflectionPermission Flags

Flag

Description

MemberAccess

Reflection of nonvisible members granted.

NoFlags

Reflection denied to nonvisible types.

ReflectionEmit

System.Reflection.Emit operations granted.

TypeInformation

This flag is now deprecated.

AllFlags

Combines TypeInformation, MemberAccess, and ReflectionEmit flags.

Attributes

Attributes extend the description of some elements of an assembly. Attributes fulfill the role of adjectives in .NET and annotate assemblies, classes, methods, parameters, and other elements of an assembly. Attributes are commonplace in .NET and fulfill many roles: They delineate serialization, stipulate import linkage, set class layout, indicate conditional compilation, mark a method as deprecated, and much more.

Attributes extend the metadata of the targeted element. An instance of the attribute is stored alongside the metadata and sometimes the MSIL code of the constituent. This is demonstrated in the following code and in Figure 10-4. The Conditional attribute marks a method for conditional compilation. Use this attribute to flag one or more symbols. If the symbol is defined, the target method and call sites are included in the compiled applications. If not defined, the method and any invocation are ignored.

image from book
Figure 10-4: The Conditional attribute in MSIL code

 #define LOG using System; using System.IO; using System.Diagnostics; namespace Donis.CSharpBook{   class Starter{     static void Main(){       LogInfo(new StreamWriter(@"c:\logfile.txt"));     }     [Conditional("LOG")]     private static void LogInfo(StreamWriter sr) {          // write information to log file     }   } } 

This is the MSIL code of the LogInfo method as displayed in ILDASM. Notice the custom directive that defines the Conditional attribute. It is integrated into the MSIL code of the method (see Figure 10-4).

Attributes are available in different flavors: predefined custom attributes, programmer-defined custom attributes, and pseudo-custom attributes.

Predefined Attributes

Predefined custom attributes, which are defined in the .NET FCL, are the most prevalent custom attributes. There is an encyclopedic list of predefined custom attributes fulfilling a variety of responsibilities in .NET. A short list of predefined custom attributes includes AssemblyVersion, Debuggable, FileIOPermission, Flags, Obsolete, and Serializable. The following code uses the Obsolete attribute to flag a method as deprecated:

 [Obsolete("Deprecated Method", false)] public static void MethodA() {   Console.WriteLine("Starter.MethodA"); } 

Combining Attributes

An entity can be assigned multiple attributes. In certain circumstances, even the same attribute can be applied multiple times. There are two ways to combine attributes: They can be grouped together or listed separately. Here, the attributes are applied separately:

 [Obsolete("Deprecated Method", false)] [FileIOPermission(SecurityAction.Demand,       Unrestricted=true)]    public static void MethodA() {   Console.WriteLine("Starter.MethodA"); } 

In the following code, two attributes are combined and applied to a method:

 [Obsolete("Deprecated Method", false),  FileIOPermission(SecurityAction.Demand,       Unrestricted=true)]    public static void MethodA() {    Console.WriteLine("Starter.MethodA"); } 

Programmer-Defined Custom Attributes

You can also create programmer-defined custom attributes to personally extend metadata of an application. There are few limitations to a programmer-defined custom attribute: Your attributes can be applied to any entity, except another programmer-defined custom attribute, and can be designed for almost any purpose.

Pseudo-Custom Attributes

Pseudo-custom attributes are interpreted by the run time and modify the metadata of the assembly. Unlike predefined or custom attributes, pseudo-custom attributes do not extend metadata. Examples of pseudo-custom attributes include DllImport, MarshalAs, and Serializable.

Anatomy of an Attribute

What exactly is an attribute? Attributes are derived from the System.Attribute class, which is the common template of all attributes. System.Attribute is an abstract class defining the intrinsic services of an attribute. Manage compilers and the run time often accord special treatment to attributes. For example, the ObsoleteAttribute causes the compiler to generate error or warning messages when a deprecated member is used.

This is the syntax of an attribute:

 [target type: attribute name(positional parameter1, positional parametern, named parameter1= value, named parametern=value)] target 

The target type designates the type of the target and must be included in the defined attribute usages. Attribute target is optional and usually inferred correctly if not omitted.

Here is a list of the valid target types:

  • field

  • event

  • method

  • Param

  • Property

  • Return

  • Type

The attribute name is the class name of the attribute. By convention, attribute names have an Attribute suffix. OneWayAttribute is representative of an attribute name. You can also use the alias of an attribute, which omits the Attribute suffix. The alias for OneWayAttribute is OneWay.

Attributes accept zero or more positional parameters. If present, position parameters are not optional and must be listed in a declared sequence. In addition, an attribute can offer any number of named parameters, which must follow positional parameters and are optional. Named parameters are not ordered and can be presented in any sequence.

The following code applies the UIPermissionAttribute to the Starter class:

 [type: UIPermissionAttribute(SecurityAction.Demand,    Clipboard=UIPermissionClipboard.OwnClipboard)] class Starter{   static void Main(){   } } 

This is the same attribute expressed somewhat more succinctly:

 [UIPermission(SecurityAction.Demand)] class Starter{   static void Main(){    } } 

Creating a Custom Attribute

You can create custom attributes for personal consumption or to be published in a library for others. There are definitive procedures to creating a programmer-defined custom attribute. Here are the steps to follow:

  1. Select an appropriate name for the custom attribute. As mentioned, attribute names should conclude with the Attribute suffix.

  2. Define an attribute class that derives from System.Attribute.

  3. Set potential targets with the AttributeUsage attribute.

  4. Implement class constructors, which determine the positional parameters.

  5. Implement write-accessible properties, which define the named parameters.

  6. Implement other members of the attribute class.

ClassVersionAttribute is a programmer-defined custom attribute that can apply a version number to types. It illuminates the steps to creating a custom attribute. The attribute assigns a target and current version number to a type. The target version is the version number of the type that the attribute adorns. The current version number is the version applied to the most recent version of the type. When the target type of the attribute is the most recent version, the target and current version are identical. In addition, the ClassVersionAttribute can request that future instances of the target type be replaced with the current type.

The first and most important task of creating a programmer-defined attribute is selecting a fabulous name. Alas, ClassVersionAttribute is a mundane but descriptive name. As an attribute, ClassVersionAttribute must inherit System.Attribute. AttributeUsageAttribute sets the potential target of the attributes. AttributeTargets is the only positional parameter of the AttributeUsageAttribute and is a bitwise enumeration. AttributeUsageAttribute has three named parameters: AllowMultiple, Inherited, and ValidOn. AllowMultiple is a Boolean flag. When true, the attribute can be applied multiple times to the same target. The default is false. Inherited is another Boolean flag. If true, which is the default, the attribute is inheritable from base classes. ValidOn is an AttributeTargets enumeration. This is an alternate method of setting the potential target of the attribute.

Here is a list of the available AttributeTargets flags:

  • All

  • Assembly

  • Class

  • Constructor

  • Delegate

  • Enum

  • Event

  • Field

  • GenericParameter

  • Interface

  • Method

  • Module

  • Parameter

  • Property

  • ReturnValue

  • Struct

This is the start of the ClassVersionAttribute class:

 [AttributeUsage(AttributeTargets.Class|AttributeTargets.Struct,     Inherited=false)]   public class ClassVersionAttribute: System.Attribute { } 

Instance constructors of an attribute provide the positional parameters for the attribute. Attributes are created at compile time, upon reflection, and when applied at run time. When attributes are created, the selected constructor runs. The positional arguments should match the signature of a constructor in the attribute. Overloading the constructor allows different sets of positional parameters. Positional and named parameters are restricted to certain types. The following is a list of available attribute parameter types.

  • bool

  • byte

  • char

  • double

  • float

  • int

  • long

  • sbyte

  • short

  • string

  • uint

  • ulong

  • ushort

ClassVersionAttribute has two overloaded constructors. The first constructor accepts the target version; the second constructor accepts the target and current version number:

 public ClassVersionAttribute(string target)   :this(target, target) {  } public ClassVersionAttribute(string target,     string current) {   m_TargetVersion=target;   m_CurrentVersion=current; } 

Define named parameters as write-only or read-write instance properties of the derived attribute class. You can duplicate positional parameters as named parameters for additional flexibility. ClassVersionAttributes offers a UseCurrentVersion and CurrentName named parameter. UseCurrentVersion is a Boolean value. If true, instances of the current type should be substituted for the target type. CurrentName names the type of the current version. This is how the named parameters are described in the ClassVersionAttribute class:

 private bool m_UseCurrentVersion=false; public bool UseCurrentVersion {   set {      if(m_TargetVersion != m_CurrentVersion) {        m_UseCurrentVersion=value;      }   }   get {     return m_UseCurrentVersion;   } } private string m_CurrentName; public string CurrentName {   set {     m_CurrentName=value;   }   get {     return m_CurrentName;   } } 

For completeness, read-only properties are added to the ClassVersionAttribute class for the target and current version. Here is the completed ClassVersionAttribute class:

 using System; namespace Donis.CSharpBook{   [AttributeUsage(AttributeTargets.Class|AttributeTargets.Struct,     Inherited=false)]   public class ClassVersionAttribute: System.Attribute {     public ClassVersionAttribute(string target)       :this(target, target) {      }     public ClassVersionAttribute(string target,         string current) {       m_TargetVersion=target;       m_CurrentVersion=current;     }     private bool m_UseCurrentVersion=false;     public bool UseCurrentVersion {       set {          if(m_TargetVersion != m_CurrentVersion) {            m_UseCurrentVersion=value;          }       }       get {         return m_UseCurrentVersion;       }     }     private string m_CurrentName;     public string CurrentName {       set {         m_CurrentName=value;       }       get {         return m_CurrentName;       }     }     private string m_TargetVersion;     public string TargetVersion {       get {         return m_TargetVersion;       }     }     private string m_CurrentVersion;     public string CurrentVersion {       get {         return m_CurrentVersion;       }     }   } } 

The ClassVersionAttribute class is compiled and published in a DLL. The following application minimally uses the ClassVersionAttribute:

 using System; namespace Donis.CSharpBook{   class Starter{     static void Main(){     }   }   [ClassVersion("1.1.2.1", UseCurrentVersion=false)]   class ZClass {   } } 

Attributes and Reflection

Programmer-defined custom attributes are valuable as information. However, the real fun and power is in associating a behavior with an attribute. You can read custom attributes with Reflection using the Attribute.GetCustomAttribute and Type.GetCustomAttributes methods. Both methods return an instance or instances of an attribute. You can downcast the attribute instance to a specific attribute type and leverage the intricacies of the attribute to implement the appropriate behavior.

Type.GetCustomAttributes returns attributes applied to a type. GetCustomAttributes is also available with other metadata elements, including Assembly.GetCustomerAttributes, MemberInfo.GetCustomAttributes, and ParameterInfo.GetCustomerAttributes. GetCustomAttributes has a single Boolean parameter. If true, the inheritance hierarchy of the type is evaluated for additional attributes.

This is the Type.GetCustomAttributes method:

 object [] GetCustomAttributes(bool inherit) object [] GetCustomAttribute(type AttributeType, bool inherit) 

Attribute.GetCustomAttribute, which is a static method, creates and returns an instance of a specific attribute. When a specific attribute is desired, GetCustomAttribute is invaluable and more efficient than GetCustomAttributes. GetCustomAttribute assumes that the attribute is assigned only once to the target. GetCustomAttribute is overloaded for each potential target, with one notable exception: GetCustomAttribute is not overloaded for a type target, which is inexplicable. Type.GetCustomAttributes is the sole option for enumerating attributes of a Type. GetCustomAttribute is overloaded for two and three arguments. The two-argument versions have the target and attribute type as parameters. The three-argument version has an additional parameter, which is the inherit parameter. If the inherit parameter is true, the ascendants of the target class are also searched for the attribute.

This is the Attribute.GetCustomAttribute method:

 static Attribute GetCustomAttribute (Assembly targetAssembly, type attributeType) static Attribute GetCustomAttribute(MemberInfo targetMember, type attributeType) static Attribute GetCustomAttribute(Module targetModule, type attributeType) static Attribute GetCustomAttribute(ParameterInfo targetParameter, type attributeType) 

This following sample code uses GetCustomAttribute:

 using System; using System.Reflection; namespace Donis.CSharpBook{   class Starter{     static void Main(){       Type tObj=typeof(Starter);       MethodInfo method=tObj.GetMethod("AMethod");       Attribute attrib=Attribute.GetCustomAttribute(         method, typeof(ObsoleteAttribute));       ObsoleteAttribute obsolete=(ObsoleteAttribute) attrib;       Console.WriteLine("Obsolete Message: "+obsolete.Message);     }     [Obsolete("Deprecated function.", false)]     public void AMethod() {     }   } } 

The following is sample code of GetCustomAttributes. The application inspects the ClassVersionAttribute attribute with the GetCustomAttributes method and acts upon the results. The application contains two versions of ZClass and both are applied to the ClassVersionAttribute. Each version of ZClass shares a common interface: IZClass. The ClassVersionAttribute of Donis.CSharpBook.ZClass lists ANamespace.ZClass as the current version or most recent version. Also, UseCurrentVersion is assigned true to indicate that the newer version should replace instances of Donis.CSharpBook.ZClass. The function CreateZClass accepts a ZClass as defined by the IZClass interface. Within the foreach loop, GetCustomAttributes enumerates the attributes of the ZClass. If the attribute is a ClassVersionAttribute, the attribute is saved and the foreach loop is exited. Next, the properties of the attribute are examined. If UseCurrentVersion is true and a current version is named, an instance of the new version is created and returned from the method. Otherwise, the target version is returned.

 using System; using System.Reflection; interface IZClass {   void AMethod(); } namespace Donis.CSharpBook{   class Starter{     static void Main(){       IZClass obj=CreateZClass(typeof(ZClass));       obj.AMethod();     }     private static IZClass CreateZClass(Type tObj) {       ClassVersionAttribute classversion=null;       foreach(Attribute attrib in tObj.GetCustomAttributes(false)) {         if(attrib.ToString()==           typeof(ClassVersionAttribute).ToString()) {           classversion=(ClassVersionAttribute) attrib;         }         else {           return null;         }       }       if(classversion.UseCurrentVersion &&           (classversion.CurrentName != null)) {         AppDomain currDomain=AppDomain.CurrentDomain;         return (IZClass) currDomain.CreateInstanceFromAndUnwrap(           "client.exe", classversion.CurrentName);       }       else {         return (IZClass) Activator.CreateInstance(tObj);       }      }   }   [ClassVersion("1.1.2.1", "2.0.0.0", UseCurrentVersion=true,     CurrentName="Donis.CSharpBook.ANamespace.ZClass")]   public class ZClass: IZClass { public void AMethod() {       Console.WriteLine("AMethod: old version");     }   }   namespace ANamespace {     [ClassVersion("2.0.0.0", UseCurrentVersion=false)]     public class ZClass: IZClass {       public void AMethod() {         Console.WriteLine("AMethod: new version");       }     }   } } 




Programming Microsoft Visual C# 2005(c) The Language
Microsoft Visual Basic 2005 BASICS
ISBN: 0619267208
EAN: 2147483647
Year: 2007
Pages: 161

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