Home Random Page


CATEGORIES:

BiologyChemistryConstructionCultureEcologyEconomyElectronicsFinanceGeographyHistoryInformaticsLawMathematicsMechanicsMedicineOtherPedagogyPhilosophyPhysicsPolicyPsychologySociologySportTourism






Nbsp;   Using Reflection to Discover a Type’s Members

So far, this chapter has focused on the parts of reflection—assembly loading, type discovery, and object construction—necessary to build a dynamically extensible application. In order to have good performance and compile-time type safety, you want to avoid using reflection as much as possible. In the dynamically extensible application scenario, after an object is constructed, the host code typi- cally casts the object to an interface type or a base class that is known at compile time; this allows the object’s members to be accessed in a high-performance and compile-time type-safe way.

In the remainder of this chapter, I’m going to focus on some other aspects of reflection that you can use to discover and then invoke a type’s members. The ability to discover and invoke a type’s members is typically used to create developer tools and utilities that analyze an assembly by looking for certain programming patterns or uses of certain members. Examples of tools/utilities that do this are ILDasm.exe, FxCopCmd.exe, and Visual Studio’s Windows Forms, Windows Presentation Founda- tion, and Web Forms designers. In addition, some class libraries use the ability to discover and invoke a type’s members in order to offer rich functionality as a convenience to developers. Examples of class libraries that do so are serialization/deserialization and simple data binding.

 

Discovering a Type’s Members

Fields, constructors, methods, properties, events, and nested types can all be defined as members within a type. The FCL contains a type called System.Reflection.MemberInfo. This class is an abstract base class that encapsulates a bunch of properties common to all type members. Derived from MemberInfo are a bunch of classes; each class encapsulates some more properties related to a specific type member. Figure 23-1 shows the hierarchy of these types.


 

System.Reflection.MethodBase
a member

 

FIGURE 23-1Hierarchy of the reflection types that encapsulate information about a type’s member.

 

The following program demonstrates how to query a type’s members and display some informa- tion about them. This code processes all of the public types defined in all assemblies loaded in the calling AppDomain. For each type, the DeclaredMembers property is called and returns a collection of MemberInfo-derived objects; each object refers to a single member defined within the type. Then, for each member, its kind (field, constructor, method, property, etc.) and its string value (obtained by calling ToString) is shown.

 

using System;

using System.Reflection;

 

public static class Program { public static void Main() {

// Loop through all assemblies loaded in this AppDomain Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies(); foreach (Assembly a in assemblies) {

Show(0, "Assembly: {0}", a);

 

// Find Types in the assembly

foreach (Type t in a.ExportedTypes) { Show(1, "Type: {0}", t);



 

// Discover the type's members

foreach (MemberInfo mi in t.GetTypeInfo().DeclaredMembers) { String typeName = String.Empty;

if (mi is Type) typeName = "(Nested) Type"; if (mi is FieldInfo) typeName = "FieldInfo";

if (mi is MethodInfo) typeName = "MethodInfo";

if (mi is ConstructorInfo) typeName = "ConstructoInfo";


if (mi is PropertyInfo) typeName = "PropertyInfo"; if (mi is EventInfo) typeName = "EventInfo"; Show(2, "{0}: {1}", typeName, mi);

}

} }

}

private static void Show(Int32 indent, String format, params Object[] args) { Console.WriteLine(new String(' ', 3 * indent) + format, args);

}

}

 

When you compile and run this code, a ton of output is produced. Here is a small sampling of what it looks like.

 

Assembly: mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 Type: System.Object

MethodInfo: System.String ToString() MethodInfo: Boolean Equals(System.Object)

MethodInfo: Boolean Equals(System.Object, System.Object) MethodInfo: Boolean ReferenceEquals(System.Object, System.Object) MethodInfo: Int32 GetHashCode()

MethodInfo: System.Type GetType() MethodInfo: Void Finalize()

MethodInfo: System.Object MemberwiseClone()

MethodInfo: Void FieldSetter(System.String, System.String, System.Object) MethodInfo: Void FieldGetter(System.String, System.String, System.Object ByRef) MethodInfo: System.Reflection.FieldInfo GetFieldInfo(System.String, System.String) ConstructoInfo: Void .ctor()

Type: System.Collections.Generic.IComparer`1[T] MethodInfo: Int32 Compare(T, T)

Type: System.Collections.IEnumerator MethodInfo: Boolean MoveNext() MethodInfo: System.Object get_Current() MethodInfo: Void Reset()

PropertyInfo: System.Object Current Type: System.IDisposable

MethodInfo: Void Dispose()

Type: System.Collections.Generic.IEnumerator`1[T] MethodInfo: T get_Current()

PropertyInfo: T Current Type: System.ArraySegment`1[T]

MethodInfo: T[] get_Array() MethodInfo: Int32 get_Offset() MethodInfo: Int32 get_Count() MethodInfo: Int32 GetHashCode()

MethodInfo: Boolean Equals(System.Object) MethodInfo: Boolean Equals(System.ArraySegment`1[T])

MethodInfo: Boolean op_Equality(System.ArraySegment`1[T], System.ArraySegment`1[T]) MethodInfo: Boolean op_Inequality(System.ArraySegment`1[T], System.ArraySegment`1[T]) ConstructoInfo: Void .ctor(T[])

ConstructoInfo: Void .ctor(T[], Int32, Int32) PropertyInfo: T[] Array

PropertyInfo: Int32 Offset PropertyInfo: Int32 Count FieldInfo: T[] _array FieldInfo: Int32 _offset


Because MemberInfo is the root of the member hierarchy, it makes sense for us to discuss it a bit more. Table 23-1 shows several read-only properties and methods offered by the MemberInfo class. These properties and methods are common to all members of a type. Don’t forget that System.

TypeInfo is derived from MemberInfo, and therefore, TypeInfo also offers all of the properties shown in Table 23-1.

 

TABLE 23-1Properties and Methods Common to All MemberInfo-Derived Types

 

Member Name Member Type Description
Name String property Returns the name of the member.
DeclaringType Type property Returns the Type that declares the member.
Module Module property Returns the Module that declares the member.
CustomAttributes Property returning IEnumerable<Custom­ AttributeData> Returns a collection in which each element identi- fies an instance of a custom attribute applied to this member. Custom attributes can be applied to any member. Even though Assembly does not derive from MemberInfo, it provides the same property that can be used with assemblies.

 

Each element of the collection returned by querying DeclaredMembers is a reference to one of the concrete types in the hierarchy. Although TypeInfo’s DeclaredMembers property returns all of the type’s members, TypeInfo also offers methods that return specific member types for a speci- fied string name. For example, TypeInfo offers GetDeclaredNestedType, GetDeclaredField, GetDeclaredMethod, GetDeclaredProperty, and GetDeclaredEvent. These methods all return a reference to a TypeInfo object, FieldInfo object, MethodInfo object, PropertyInfo object, or EventInfo object, respectively. There is also a GetDeclaredMethods method that returns a collec- tion of MethodInfo objects describing the methods matching the specified string name.

Figure 23-2 summarizes the types used by an application to walk reflection’s object model. From an AppDomain, you can discover the assemblies loaded into it. From an assembly, you can discover the modules that make it up. From an assembly or a module, you can discover the types that it de- fines. From a type, you can discover its nested types, fields, constructors, methods, properties, and events. Namespaces are not part of this hierarchy because they are simply syntactical gatherings of types. If you want to list all of the namespaces defined in an assembly, you need to enumerate all of the types in this assembly and take a look at their Namespace property.

From a type, it is also possible to discover the interfaces it implements. And from a constructor, method, property accessor method, or event add/remove method, you can call the GetParameters method to obtain an array of ParameterInfo objects, which tells you the types of the member’s parameters. You can also query the read-only ReturnParameter property to get a ParameterInfo object for detailed information about a member’s return type. For a generic type or method, you can call the GetGenericArguments method to get the set of type parameters. Finally, for any of these items, you can query the CustomAttributes property to obtain the set of custom attributes applied to them.


     
 
FieldInfo #1  
  FieldInfo #2
     

 

 
 
ConstructorInfo #1  
  ConstructorInfo #2
     

 

 
 
MethodInfo #1  
  MethodInfo #2
     

 


 

 

PropertyInfo #1  
  PropertyInfo #2
     

 

EventInfo #1  
  EventInfo #2
     

 

FIGURE 23-2Types an application uses to walk reflection’s object model.

 

 

Invoking a Type’s Members

Now that you know how to discover the members defined by a type, you may want to invoke one of these members. What invoke means depends on the kind of member being invoked. Table 23-2 shows which method to call for each kind of member to invoke that member.

 

TABLE 23-2How to Invoke a Member

 

Type of Member Method to Invoke Member
FieldInfo Call GetValue to get a field’s value. Call SetValue to set a field’s value.
ConstructorInfo Call Invoke to construct an instance of the type and call a constructor.
MethodInfo Call Invoke to call a method of the type.
PropertyInfo Call GetValue to call a property’s get accessor method. Call SetValue to call a property’s set accessor method.
EventInfo Call AddEventHandler to call an event’s add accessor method. Call RemoveEventHandler to call an event’s remove accessor method.

 

The PropertyInfo type represents metadata information about a property (as discussed in Chapter 10, “Properties”); that is, PropertyInfo offers CanRead, CanWrite, and PropertyType read-only properties. These properties indicate whether a property is readable or writeable and what data type the property is. PropertyInfo also has read-only GetMethod and SetMethod proper- ties, which return MethodInfo objects representing the methods that get and set a property’s value.


PropertyInfo’s GetValue and SetValue methods exist for convenience; internally, they invoke the appropriate MethodInfo object. To support parameterful properties (C# indexers), the GetValue and SetValue methods offer an index parameter of Object[] type.

The EventInfo type represents metadata information about an event (as discussed in Chapter 11, “Events”). The EventInfo type offers a read-only EventHandlerType property that returns the Type of the event’s underlying delegate. The EventInfo type also has read-only AddMethod and RemoveMethod properties, which return the MethodInfo objects corresponding to the methods that add or remove a delegate to/from the event. To add or remove a delegate, you can invoke these

MethodInfo objects, or you can call EventInfo’s more convenient AddEventHandler and Remove­ EventHandler methods.

The following sample application demonstrates the various ways to use reflection to access a type’s members. The SomeType class represents a type that has various members: a private field (m_some­ Field), a public constructor (SomeType) that takes an Int32 argument passed by reference, a public method (ToString), a public property (SomeProp), and a public event (SomeEvent). Having defined the SomeType type, I offer three different methods that use reflection to access SomeType’s mem- bers. Each method uses reflection in a different way to accomplish the same thing.

■ The BindToMemberThenInvokeTheMember method demonstrates how to bind to a member and invoke it later.

■ The BindToMemberCreateDelegateToMemberThenInvokeTheMember method demon- strates how to bind to an object or member, and then it creates a delegate that refers to that object or member. Calling through the delegate is very fast, and this technique yields faster performance if you intend to invoke the same member on the same object multiple times.

■ The UseDynamicToBindAndInvokeTheMember method demonstrates how to use C# dynamic primitive type (discussed at the end of Chapter 5, “Primitive, Reference, and Value Types”) to simplify the syntax for accessing members. In addition, this technique can give reasonably good performance if you intend to invoke the same member on different objects that are all of the same type because the binding will happen once per type and be cached so that it can be invoked multiple times quickly. You can also use this technique to invoke a member on objects of different types.

 

using System;

using System.Reflection;

using Microsoft.CSharp.RuntimeBinder; using System.Linq;

 

 

// This class is used to demonstrate reflection

// It has a field, constructor, method, property, and an event internal sealed class SomeType {

private Int32 m_someField;

public SomeType(ref Int32 x) { x *= 2; }

public override String ToString() { return m_someField.ToString(); } public Int32 SomeProp {


get { return m_someField; } set {

if (value < 1)

throw new ArgumentOutOfRangeException("value"); m_someField = value;

}

public event EventHandler SomeEvent;

private void NoCompilerWarnings() { SomeEvent.ToString();}

}

 

public static class Program { public static void Main() {

Type t = typeof(SomeType); BindToMemberThenInvokeTheMember(t); Console.WriteLine();

 

BindToMemberCreateDelegateToMemberThenInvokeTheMember(t); Console.WriteLine();

 

UseDynamicToBindAndInvokeTheMember(t); Console.WriteLine();

}

 

 

private static void BindToMemberThenInvokeTheMember(Type t) { Console.WriteLine("BindToMemberThenInvokeTheMember");

 

// Construct an instance

Type ctorArgument = Type.GetType("System.Int32&"); // or typeof(Int32).MakeByRefType(); ConstructorInfo ctor = t.GetTypeInfo().DeclaredConstructors.First(

c => c.GetParameters()[0].ParameterType == ctorArgument); Object[] args = new Object[] { 12 }; // Constructor arguments Console.WriteLine("x before constructor called: " + args[0]); Object obj = ctor.Invoke(args);

Console.WriteLine("Type: " + obj.GetType()); Console.WriteLine("x after constructor returns: " + args[0]);

 

// Read and write to a field

FieldInfo fi = obj.GetType().GetTypeInfo().GetDeclaredField("m_someField"); fi.SetValue(obj, 33);

Console.WriteLine("someField: " + fi.GetValue(obj));

 

// Call a method

MethodInfo mi = obj.GetType().GetTypeInfo().GetDeclaredMethod("ToString"); String s = (String)mi.Invoke(obj, null);

Console.WriteLine("ToString: " + s);

 

// Read and write a property

PropertyInfo pi = obj.GetType().GetTypeInfo().GetDeclaredProperty("SomeProp"); try {

pi.SetValue(obj, 0, null);

}

catch (TargetInvocationException e) {

if (e.InnerException.GetType() != typeof(ArgumentOutOfRangeException)) throw;


Console.WriteLine("Property set catch.");

}

pi.SetValue(obj, 2, null);

Console.WriteLine("SomeProp: " + pi.GetValue(obj, null));

 

// Add and remove a delegate from the event

EventInfo ei = obj.GetType().GetTypeInfo().GetDeclaredEvent("SomeEvent"); EventHandler eh = new EventHandler(EventCallback); // See ei.EventHandlerType ei.AddEventHandler(obj, eh);

ei.RemoveEventHandler(obj, eh);

}

 

// Callback method added to the event

private static void EventCallback(Object sender, EventArgs e) { }

 

private static void BindToMemberCreateDelegateToMemberThenInvokeTheMember(Type t) { Console.WriteLine("BindToMemberCreateDelegateToMemberThenInvokeTheMember");

 

// Construct an instance (You can't create a delegate to a constructor) Object[] args = new Object[] { 12 }; // Constructor arguments Console.WriteLine("x before constructor called: " + args[0]);

Object obj = Activator.CreateInstance(t, args); Console.WriteLine("Type: " + obj.GetType().ToString()); Console.WriteLine("x after constructor returns: " + args[0]);

 

// NOTE: You can't create a delegate to a field

 

// Call a method

MethodInfo mi = obj.GetType().GetTypeInfo().GetDeclaredMethod("ToString"); var toString = mi.CreateDelegate<Func<String>>(obj);

String s = toString(); Console.WriteLine("ToString: " + s);

 

// Read and write a property

PropertyInfo pi = obj.GetType().GetTypeInfo().GetDeclaredProperty("SomeProp"); var setSomeProp = pi.SetMethod.CreateDelegate<Action<Int32>>(obj);

try {

setSomeProp(0);

}

catch (ArgumentOutOfRangeException) { Console.WriteLine("Property set catch.");

}

setSomeProp(2);

var getSomeProp = pi.GetMethod.CreateDelegate<Func<Int32>>(obj); Console.WriteLine("SomeProp: " + getSomeProp());

 

// Add and remove a delegate from the event

EventInfo ei = obj.GetType().GetTypeInfo().GetDeclaredEvent("SomeEvent"); var addSomeEvent = ei.AddMethod.CreateDelegate<Action<EventHandler>>(obj); addSomeEvent(EventCallback);

var removeSomeEvent = ei.RemoveMethod.CreateDelegate<Action<EventHandler>>(obj); removeSomeEvent(EventCallback);

}


private static void UseDynamicToBindAndInvokeTheMember(Type t) { Console.WriteLine("UseDynamicToBindAndInvokeTheMember");

 

// Construct an instance (You can't use dynamic to call a constructor) Object[] args = new Object[] { 12 }; // Constructor arguments Console.WriteLine("x before constructor called: " + args[0]);

dynamic obj = Activator.CreateInstance(t, args); Console.WriteLine("Type: " + obj.GetType().ToString()); Console.WriteLine("x after constructor returns: " + args[0]);

 

// Read and write to a field try {

obj.m_someField = 5;

Int32 v = (Int32)obj.m_someField; Console.WriteLine("someField: " + v);

}

catch (RuntimeBinderException e) {

// We get here because the field is private Console.WriteLine("Failed to access field: " + e.Message);

}

 

// Call a method

String s = (String)obj.ToString(); Console.WriteLine("ToString: " + s);

 

// Read and write a property try {

obj.SomeProp = 0;

}

catch (ArgumentOutOfRangeException) { Console.WriteLine("Property set catch.");

}

obj.SomeProp = 2;

Int32 val = (Int32)obj.SomeProp; Console.WriteLine("SomeProp: " + val);

 

// Add and remove a delegate from the event obj.SomeEvent += new EventHandler(EventCallback); obj.SomeEvent ­= new EventHandler(EventCallback);

}

}

 

 

internal static class ReflectionExtensions {

// Helper extension method to simplify syntax to create a delegate

public static TDelegate CreateDelegate<TDelegate>(this MethodInfo mi, Object target = null) { return (TDelegate)(Object)mi.CreateDelegate(typeof(TDelegate), target);

}

}

 

If you build and run this code, you’ll see the following output.

 

BindToMemberThenInvokeTheMember x before constructor called: 12 Type: SomeType

x after constructor returns: 24


someField: 33

ToString: 33 Property set catch. SomeProp: 2

 

BindToMemberCreateDelegateToMemberThenInvokeTheMember x before constructor called: 12

Type: SomeType

x after constructor returns: 24 ToString: 0

Property set catch. SomeProp: 2

 

UseDynamicToBindAndInvokeTheMember x before constructor called: 12 Type: SomeType

x after constructor returns: 24

Failed to access field: 'SomeType.m_someField' is inaccessible due to its protection level ToString: 0

Property set catch. SomeProp: 2

 

Notice that SomeType’s constructor takes an Int32 by reference as its only parameter. The previ- ous code shows how to call this constructor and how to examine the modified Int32 value after the constructor returns. Near the top of the BindToMemberThenInvokeTheMember method, I show how to accomplish this by calling Type’s GetType method passing in a string of "System.Int32&". The ampersand (&) in the string allows me to identify a parameter passed by reference. This ampersand is part of the Backus-Naur Form grammar for type names, which you can look up in the FCL documen- tation. The code also shows how to accomplish the same thing using Type’s MakeByRefType method.

 

 

Using Binding Handles to Reduce Your Process’s Memory Consumption

Many applications bind to a bunch of types (Type objects) or type members (MemberInfo-derived objects) and save these objects in a collection of some sort. Then later, the application searches the collection for a particular object and then invokes this object. This is a fine way of doing things except for one small issue: Type and MemberInfo-derived objects require a lot of memory. So if an applica- tion holds on to too many of these objects and invokes them occasionally, the application’s memory consumption increases dramatically, having an adverse effect on the application’s performance.

Internally, the CLR has a more compact way of representing this information. The CLR creates these objects for our applications only to make things easier for developers. The CLR doesn’t need these big objects itself in order to run. Developers who are saving/caching a lot of Type and MemberInfo- derived objects can reduce their working set by using run-time handles instead of objects. The FCL defines three runtime handle types (all defined in the System namespace): RuntimeTypeHandle, RuntimeFieldHandle, and RuntimeMethodHandle. All of these types are value types that contain just one field, an IntPtr; this makes instances of these types cheap (memory-wise). The IntPtr field is a handle that refers to a type, field, or method in an AppDomain’s loader heap. So what you need now is an easy and efficient way to convert a heavyweight Type/MemberInfo object to a lightweight


run-time handle instance and vice versa. Fortunately, this is easy using the following conversion meth- ods and properties:

■ To convert a Type object to a RuntimeTypeHandle, call Type’s static GetTypeHandle

method passing in the reference to the Type object.

 

■ To convert a RuntimeTypeHandle to a Type object, call Type’s static GetTypeFromHandle

method passing in the RuntimeTypeHandle.

 

■ To convert a FieldInfo object to a RuntimeFieldHandle, query FieldInfo’s instance read-only FieldHandle property.

■ To convert a RuntimeFieldHandle to a FieldInfo object, call FieldInfo’s static Get­ FieldFromHandle method.

■ To convert a MethodInfo object to a RuntimeMethodHandle, query MethodInfo’s instance read-only MethodHandle property.

■ To convert a RuntimeMethodHandle to a MethodInfo object, call MethodInfo’s static Get­ MethodFromHandle method.

The following program sample acquires a lot of MethodInfo objects, converts them to Runtime­ MethodHandle instances, and shows the working set difference.

 

using System;

using System.Reflection;

using System.Collections.Generic;

 

public sealed class Program {

private const BindingFlags c_bf = BindingFlags.FlattenHierarchy | BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic;

 

public static void Main() {

// Show size of heap before doing any reflection stuff Show("Before doing anything");

 

// Build cache of MethodInfo objects for all methods in MSCorlib.dll List<MethodBase> methodInfos = new List<MethodBase>();

foreach (Type t in typeof(Object).Assembly.GetExportedTypes()) {

// Skip over any generic types

if (t.IsGenericTypeDefinition) continue;

 

MethodBase[] mb = t.GetMethods(c_bf); methodInfos.AddRange(mb);

}

 

// Show number of methods and size of heap after binding to all methods Console.WriteLine("# of methods={0:N0}", methodInfos.Count); Show("After building cache of MethodInfo objects");


// Build cache of RuntimeMethodHandles for all MethodInfo objects List<RuntimeMethodHandle> methodHandles =

methodInfos.ConvertAll<RuntimeMethodHandle>(mb => mb.MethodHandle);

 

Show("Holding MethodInfo and RuntimeMethodHandle cache"); GC.KeepAlive(methodInfos); // Prevent cache from being GC'd early

 

methodInfos = null; // Allow cache to be GC'd now Show("After freeing MethodInfo objects");

 

methodInfos = methodHandles.ConvertAll<MethodBase>( rmh=> MethodBase.GetMethodFromHandle(rmh));

Show("Size of heap after re­creating MethodInfo objects"); GC.KeepAlive(methodHandles); // Prevent cache from being GC'd early GC.KeepAlive(methodInfos); // Prevent cache from being GC'd early

 

methodHandles = null; // Allow cache to be GC'd now methodInfos = null; // Allow cache to be GC'd now Show("After freeing MethodInfos and RuntimeMethodHandles");

}

}

 

When I compiled and executed this program, I got the following output.

 

Heap size= 85,000 ­ Before doing anything

# of methods=48,467

Heap size= 7,065,632 ­ After building cache of MethodInfo objects

Heap size= 7,453,496 ­ Holding MethodInfo and RuntimeMethodHandle cache Heap size= 6,732,704 ­ After freeing MethodInfo objects

Heap size= 7,372,704 ­ Size of heap after re­creating MethodInfo objects Heap size= 192,232 ­ After freeing MethodInfos and RuntimeMethodHandles


C H AP T E R 24


Date: 2016-03-03; view: 800


<== previous page | next page ==>
Nbsp;   Designing an Application That Supports Add-Ins | Overriding the Assembly and/or Type
doclecture.net - lectures - 2014-2024 year. Copyright infringement or personal data (0.026 sec.)