Click here to Skip to main content
15,886,761 members
Articles / Programming Languages / C#

Examining an Assembly at Runtime

Rate me:
Please Sign up or sign in to vote.
4.68/5 (11 votes)
10 Aug 2009CPOL10 min read 41K   35   5
An article that desribes how to use the Reflection API.

Preface – Some Words about Reflection

Often in .NET documentation, there is a look at the CLR and its Common Type System (CTS) from the standpoint of code written in a high-level language (C#, VB, Managed C++) that compiles into IL. Such code interacts statically with the CLR, meaning that compilers decide during compilation (using early binding) what types your program will work with and what code will get executed. Conversely, dynamic code defers decisions to runtime (using techniques like late binding), enabling programs to use information only available at runtime to make decisions like what types to use and what code to execute. This can even include some degree of dynamic code generation. Having said that, we can conclude that there is a contrast between API functions and classes that encapsulate a number of methods to give you an easy interface for inspecting and emitting metadata. Some constructs built right into the CLR’s type system fall in between the two categories. For example, virtual methods delay until runtime the decision of precisely what code to execute by using an object identity-based virtual table lookup. Even JIT can be considered a form of dynamism because the actual native code that gets executed isn’t generated until runtime (except in the case of NGen). But in addition to those, there is a set of libraries that enable you to take it a step further by manipulating metadata, instantiating types dynamically, executing bits of IL, and even generating new metadata to be saved to disk or executed. This interaction occurs through runtime API interactions with CLR data structures, and not by statically emitted IL. This is called Reflection. To load and inspect an assembly to determine what type it supports, use a set of classes supported by the .NET Framework which are collectively called the Reflection API.

Examining an Assembly

Since we can see that the Reflection system in .NET is used to interrogate types in the type system as well as to create code on the fly, we can start to examine an assembly by instantiating the Assembly class. The Assembly class supports a number of static methods to create instances of the class:

  • GetAssembly: returns an assembly that contains a specified type.
  • GetCallingAssembly: returns the assembly that contains the code that called the current method.
  • GetEntryAssembly: returns the assembly that contains code that started up the current process.
  • GetExecutingAssembly: returns the assembly that contains the currently executing code.
  • Load: loads an assembly into the current AppDomain.
  • LoadFile: loads an assembly by specifying the path.
  • LoadFrom: loads an assembly into the current AppDomain located at a specific path.

Once you have an instance of the Assembly class, you can interrogate the properties of an assembly itself. Before listing the Assembly class’ instance properties and methods, we will look at a basic example. Assume we write some code to display a multiplication table:

C#
using System;
class App {
static void Main() {
for (int i = 1; i < 100; i+=10)
 {
  for (int j = i; j < i + 10; ++j)
     {
     Console.Write(" "  + j);
     } 
 Console.WriteLine();
      }
  }
}

We compile the code, loop.cs, to get this output:

1 2 3 4 5 6 7 8 9 10
11 12 13 14 15 16 17 18 19 20
21 22 23 24 25 26 27 28 29 30
31 32 33 34 35 36 37 38 39 40
41 42 43 44 45 46 47 48 49 50
51 52 53 54 55 56 57 58 59 60
61 62 63 64 65 66 67 68 69 70
71 72 73 74 75 76 77 78 79 80
81 82 83 84 85 86 87 88 89 90
91 92 93 94 95 96 97 98 99 100

The code uses two for loop constructs, imports a namespace, declares the App class, declares the Main method, uses the Console.WriteLine() method statement, and ends the class. Simple enough, right? Then, we know that by default, the App class inherits all of the methods of System.Object, the root class. So, let’s write code to instantiate the Assembly class, and then interrogate some of its properties:

C#
using System;
using System.IO;
using System.Reflection;
public sealed class Program {
public static int Main() {
Assembly a  = Assembly.LoadFrom("loop.exe");
Module[] m = a.GetModules();
Type[]  types = m[0].GetTypes();
Type type = types[0];
Console.WriteLine("Type ({0}) has these methods:",  type.Name);
MethodInfo[] mInfo = type.GetMethods();
 foreach (MethodInfo mi in mInfo)
     {
   Console.WriteLine("    {0}", mi);
     }
  return 0;
   }
}

We compile this simple program to get this output:

Type (App) has these methods:
    System.String ToString()
    Boolean Equals(System.Object)
    Int32 GetHashCode()
    System.Type GetType()

We first tell the compiler that we want to use the classes of the System.Reflection namespace because we want to inspect metadata. In Main(), we load the assembly by a physical name, loop.exe, so we make sure that this PE file resides in the directory as our code. Next, we ask the loaded assembly for the array of modules it contains. From this array of modules, we pull off the array of types supported by the module, and from this array of types, we pull off the first type. For loop.exe, the first and only type happens to be App. Once we have obtained this type or class, we loop through the list of its exposed methods. Even though we only wrote the Main method, there are four additional methods that it supports via inheritance from System.Object.

Note the Assembly Class Properties

  • EntryPoint: gets the method that represents the first code to be executed in an assembly.
  • FullName: gets the fully qualified name of an assembly.
  • GlobalAssemblyCache: gets a value that indicates whether an assembly was loaded from the GAC.
  • Location: gets the path to an assembly.

Module Methods

  • FindTypes: searches the module for types matching certain criteria.
  • GetCustomAttributes: gets the attributes associated with a module.
  • GetField: returns a specific field in a module.
  • GetFields: returns all the fields in a module.
  • GetMethod: returns a specific method in a module.
  • GetMethods: returns all the methods in a module.
  • GetTypes: returns all the types in a module.
  • IsResource: used to determine if certain objects are resources in a module.

Shown below is code that makes use of these operations:

C#
using System;
using System.IO;
using System.Reflection;
public sealed class Program {
    public static void Main(string[]  args) {
        string path = @"C:\Windows\Microsoft.NET\" + 
                      @"Framework\v2.0.50727\System.dll";
        Assembly a = Assembly.LoadFile(path);
        ShowAssemblyInfo(a);
        Assembly ourAssembly = Assembly.GetExecutingAssembly();
        ShowAssemblyInfo(ourAssembly);
        Console.Read();
    }
    static void ShowAssemblyInfo(Assembly a )
    {
        Console.WriteLine(a.FullName);
        Console.WriteLine("From GAC?  {0}", a.GlobalAssemblyCache);
        Console.WriteLine("Path:      {0}", a.Location);
        Console.WriteLine("Version:   {0}", a.ImageRuntimeVersion); 
        // show modules

        foreach (Module m in a.GetModules())
        {
            Console.WriteLine("   Mod: {0}",  m.Name);
        } 
        Console.WriteLine();
    }
}

Note the Assembly Class Methods

  • CreateInstance: creates an instance of a specified type that exists in an assembly.
  • GetCustomAttributes: returns an array of attributes for an assembly.
  • GetExportedTypes: returns a collection of types that are publicly visible outside an assembly.
  • GetFile: returns a FileStream object for a file contained in the resources of an assembly.
  • GetFiles: returns an array of FileStream objects that represents all files contained in the resources of an assembly.
  • GetLoadedModules: returns an array of currently loaded modules in an assembly.
  • GetModule: returns a specified module from an assembly.
  • GetModules: returns all of the modules from an assembly.
  • GetName: returns an AssemblyName object that represents the fully qualified name of an assembly.
  • GetTypes: returns an array of all of the types defined in all modules of an assembly.
  • IsDefined: returns a value that indicates whether a specific attribute is defined in an assembly.

Each assembly contains one or more modules that represent containers for type information. You can ask the Assembly class to return the modules in an assembly by calling the GetModules method. The Module class’ properties are shown below:

  • Assembly: gets the assembly that this module resides in.
  • FullyQualifedName: gets the full name to this module, including the path to the module, if any.
  • Name: gets the name of the module (without the path to the module).

Output

System, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
From GAC?  True
Path:      C:\Windows\assembly\GAC_MSIL\System\2.0.0.0__b77a5c561934e089\System.
dll
Version:   v2.0.50727
  Mod: System.dll

show, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null
From GAC?  False
Path:      C:\Windows\MICROS~1.NET\FRAMEW~1\V20~1.507\show.exe
Version:   v2.0.50727
  Mod: show.exe

Assembly Loading and Using Reflection to Discover Types

When the Just-In-Time compiler compiles the IL for a method, it sees what types are referenced in the IL code. Then at runtime, the JIT compiler uses the assembly’s TypeRef and AssemblyRef metadata tables to determine what assembly defines the type being referenced. The AssemblyRef metadata table entry contains all of the parts that make up the strong name of the assembly. The JIT compiler grabs all of those parts – name, version, culture, and public key token – concatenates them into a string, and then attempts to load an assembly matching this identity into the AppDomain. Internally, the CLR attempts to load this assembly using the System.Reflection.Assembly class’ static Load method. This method is the CLR’s equivalent of Win32’s LoadLibrary function. Reflection is used to determine what types an assembly defines. Here is an example of code that loads an assembly (by calling Load explicitly) and shows the names of all of the publicly exported types defined in it:

C#
using System;
using System.Reflection;

public sealed class Program {
   public static void Main() {
      String dataAssembly = "System.Data, version=2.0.0.0, " +
         "culture=neutral, PublicKeyToken=b77a5c561934e089";
      LoadAssemAndShowPublicTypes(dataAssembly);
   }

   private static void LoadAssemAndShowPublicTypes(String assemId) {

      // Explicitly load an assembly in to this AppDomain
      Assembly a = Assembly.Load(assemId);

      // Execute this loop once for each Type 
      // publicly-exported from the loaded assembly 
      foreach (Type t in a.GetExportedTypes()) {

         // Display the full name of the type
         Console.WriteLine(t.FullName);
      }
   }
}

The Output:

Microsoft.SqlServer.Server.SqlContext
System.Data.IDataRecord
Microsoft.SqlServer.Server.SqlDataRecord
Microsoft.SqlServer.Server.SqlPipe
Microsoft.SqlServer.Server.SqlTriggerContext
Microsoft.SqlServer.Server.TriggerAction
System.Data.AcceptRejectRule
System.Data.InternalDataCollectionBase
System.Data.Common.CatalogLocation
System.Data.CommandBehavior
System.Data.CommandType
System.Data.ConflictOption
System.Data.ConnectionState
System.Data.Constraint
System.Data.ConstraintCollection
System.Data.DataColumn
System.Data.DataColumnChangeEventArgs
System.Data.DataColumnChangeEventHandler
System.Data.DataColumnCollection
System.Data.DataException
System.Data.ConstraintException
System.Data.DeletedRowInaccessibleException
System.Data.DuplicateNameException
System.Data.InRowChangingEventException
System.Data.InvalidConstraintException
System.Data.MissingPrimaryKeyException
System.Data.NoNullAllowedException
System.Data.ReadOnlyException
System.Data.RowNotInTableException
System.Data.VersionNotFoundException
System.Data.DataRelation
System.Data.DataRelationCollection
System.Data.DataRow
System.Data.DataRowBuilder
System.Data.DataRowAction
System.Data.DataRowChangeEventArgs
System.Data.DataRowChangeEventHandler
System.Data.DataRowCollection
System.Data.DataRowState
System.Data.DataRowVersion
System.Data.DataRowView
System.Data.SerializationFormat
System.Data.DataSet
System.Data.DataSetSchemaImporterExtension
System.Data.DataSetDateTime
System.Data.DataSysDescriptionAttribute
System.Data.DataTable
System.Data.DataTableClearEventArgs
System.Data.DataTableClearEventHandler
System.Data.DataTableCollection
System.Data.DataTableNewRowEventHandler
System.Data.DataTableNewRowEventArgs
System.Data.IDataReader
System.Data.Common.DbDataReader
System.Data.DataTableReader
System.Data.DataView
System.Data.DataViewManager
System.Data.DataViewRowState
System.Data.DataViewSetting
System.Data.DataViewSettingCollection
System.Data.DBConcurrencyException
System.Data.DbType
System.Data.FillErrorEventArgs
System.Data.FillErrorEventHandler
System.Data.ForeignKeyConstraint
System.Data.IColumnMapping
System.Data.IColumnMappingCollection
System.Data.IDataAdapter
System.Data.IDataParameter
System.Data.IDataParameterCollection
System.Data.IDbCommand
System.Data.IDbConnection
System.Data.IDbDataAdapter
System.Data.IDbDataParameter
System.Data.IDbTransaction
System.Data.IsolationLevel
System.Data.ITableMapping
System.Data.ITableMappingCollection
System.Data.LoadOption
System.Data.MappingType
System.Data.MergeFailedEventArgs
System.Data.MergeFailedEventHandler
System.Data.MissingMappingAction
System.Data.MissingSchemaAction
System.Data.OperationAbortedException
System.Data.ParameterDirection
System.Data.PropertyCollection
System.Data.StatementCompletedEventArgs
System.Data.StatementCompletedEventHandler
System.Data.Rule
System.Data.SchemaType
System.Data.SchemaSerializationMode
System.Data.SqlDbType
System.Data.StateChangeEventArgs
System.Data.StateChangeEventHandler
System.Data.StatementType
System.Data.UniqueConstraint
System.Data.UpdateRowSource
System.Data.UpdateStatus
System.Data.XmlReadMode
System.Data.XmlWriteMode
System.Data.TypedDataSetGenerator
System.Data.StrongTypingException
System.Data.TypedDataSetGeneratorException
System.Data.Common.DataAdapter
System.Data.Common.DataColumnMapping
System.Data.Common.DataColumnMappingCollection
System.Data.Common.DbDataRecord
System.Data.Common.DataTableMapping
System.Data.Common.DataTableMappingCollection
System.Data.Common.DbCommand
System.Data.Common.DbCommandBuilder
System.Data.Common.DbConnection
System.Data.Common.DbConnectionStringBuilder
System.Data.Common.DbDataAdapter
System.Data.Common.DBDataPermission
System.Data.Common.DBDataPermissionAttribute
System.Data.KeyRestrictionBehavior
System.Data.Common.DbDataSourceEnumerator
System.Data.Common.DbEnumerator
System.Data.Common.DbException
System.Data.Common.DbParameter
System.Data.Common.DbParameterCollection
System.Data.Common.DbProviderConfigurationHandler
System.Data.Common.DbProviderFactories
System.Data.Common.DbProviderFactoriesConfigurationHandler
System.Data.Common.DbProviderFactory
System.Data.Common.DbProviderSpecificTypePropertyAttribute
System.Data.Common.DbTransaction
System.Data.Common.GroupByBehavior
System.Data.Common.IdentifierCase
System.Data.Common.RowUpdatedEventArgs
System.Data.Common.RowUpdatingEventArgs
System.Data.Common.SchemaTableColumn
System.Data.Common.SchemaTableOptionalColumn
System.Data.Common.SupportedJoinOperators
System.Data.InvalidExpressionException
System.Data.EvaluateException
System.Data.SyntaxErrorException
System.Data.Odbc.OdbcCommand
System.Data.Odbc.OdbcCommandBuilder
System.Data.Odbc.OdbcConnection
System.Data.Odbc.OdbcConnectionStringBuilder
System.Data.Odbc.OdbcDataAdapter
System.Data.Odbc.OdbcDataReader
System.Data.Odbc.OdbcError
System.Data.Odbc.OdbcErrorCollection
System.Data.Odbc.OdbcException
System.Data.Odbc.OdbcFactory
System.Data.Odbc.OdbcInfoMessageEventHandler
System.Data.Odbc.OdbcInfoMessageEventArgs
System.Data.Odbc.OdbcMetaDataCollectionNames
System.Data.Odbc.OdbcMetaDataColumnNames
System.Data.Odbc.OdbcParameter
System.Data.Odbc.OdbcParameterCollection
System.Data.Odbc.OdbcPermission
System.Data.Odbc.OdbcPermissionAttribute
System.Data.Odbc.OdbcRowUpdatingEventHandler
. . and on and on . . . 
System.Data.OleDb.OleDbRowUpdatedEventArgs
System.Data.OleDb.OleDbRowUpdatedEventHandler
System.Data.OleDb.OleDbRowUpdatingEventArgs
System.Data.OleDb.OleDbRowUpdatingEventHandler
System.Data.OleDb.OleDbSchemaGuid
System.Data.OleDb.OleDbTransaction
System.Data.OleDb.OleDbType
System.Data.PropertyAttributes
System.Data.Common.DbMetaDataCollectionNames
System.Data.Common.DbMetaDataColumnNames
Microsoft.SqlServer.Server.IBinarySerialize
Microsoft.SqlServer.Server.InvalidUdtException
System.Data.Sql.SqlDataSourceEnumerator
Microsoft.SqlServer.Server.SqlFacetAttribute
Microsoft.SqlServer.Server.DataAccessKind
Microsoft.SqlServer.Server.SystemDataAccessKind
Microsoft.SqlServer.Server.SqlFunctionAttribute
Microsoft.SqlServer.Server.SqlMetaData
Microsoft.SqlServer.Server.SqlMethodAttribute
System.Data.Sql.SqlNotificationRequest
Microsoft.SqlServer.Server.SqlProcedureAttribute
Microsoft.SqlServer.Server.SqlTriggerAttribute
Microsoft.SqlServer.Server.SqlUserDefinedAggregateAttribute
Microsoft.SqlServer.Server.Format
Microsoft.SqlServer.Server.SqlUserDefinedTypeAttribute
. . . the list goes on
System.Data.SqlTypes.SqlXml
System.Xml.XmlDataDocument

Reflecting Types

Getting Types

Before you can start looking at type information, you have to understand how to get Type objects. Recall the code above and reexamine how it iterates over an array of System.Type objects. The System.Type type is the starting point for doing type and object manipulations. System.Type is an abstract base type derived from System.Reflection.MemberInfo (because a Type can be a member of another type). When working with an Assembly instance, you can ask it for all the types in the modules of the assembly by calling the GetTypes method. You can, however, get a Type object in several ways:

  • From the Assembly class
  • From the Modules class
  • From instances of Object
  • Using the typeof keyword in C#

Now, let’s examine these means to get a Type object:

C#
Assembly a = Assembly.GetExecutingAssembly();
// get the types in the assembly
Type[]  types = a.GetTypes();

When working with a module object, you can ask for all the types associated with it by calling the Module class’ GetTypes method:

C#
Module[] mods = a.GetModules();
Module m = mods[0];
// get all the types from a module
Type[] moduleTypes = m.GetTypes();

You can create a Type object using the C# typeof keyword:

C#
Type specficType = typeof(Int32);

Now that we can get a Type object, we instantiate Type to get information about the type, like so:

C#
using System;
class App {
    static void Main() {
        Type t = typeof(String);
        Console.WriteLine("Type:  {0}", t.Name);
        Console.WriteLine("   Namespace     : {0}",  t.Namespace);
        Console.WriteLine("   FullName      : {0}",  t.FullName);
        Console.WriteLine("   IsValueType?  : {0}", t.IsValueType);
        Console.WriteLine("   IsSealed?     : {0}", t.IsSealed);
        Console.WriteLine("   IsAbstract?   : {0}", t.IsAbstract);
        Console.WriteLine("   IsPublic? ?   : {0}", t.IsPublic);
        Console.WriteLine("   IsClass?  ?   : {0}", t.IsClass);
    }
}

Output

Type:  String
Namespace     : System
FullName      : System.String
IsValueType?  : False
IsSealed?     : True
IsAbstract?   : False
IsPublic? ?   : True
IsClass?  ?   : True

Within the Type class, there are methods for getting different parts of a type, including methods, properties, fields, and events. Each of these parts of a Type is represented by a class within the Reflection system that ends with the name Info. The following code illustrates discovering a Type’s members, how to query them, and process all of the public types defined in all of the assemblies loaded in the calling AppDomain. For each type, the GetMembers method is called, and returns an array of MemberInfo-derived objects, each object refers to a single member defined within the type. The BindingFlags variable bf, passed to the GetMembers method, tells the method which kinds of members to return. BindingFlags will be discussed shortly:

C#
using System;
using System.Reflection;

public sealed 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.GetExportedTypes()) {
            Show(1, "Type: {0}", t);

            // Indicate the kinds of members we want to discover
            const BindingFlags bf = BindingFlags.DeclaredOnly |
               BindingFlags.NonPublic | BindingFlags.Public |
               BindingFlags.Instance | BindingFlags.Static;

            // Discover the type's members
            foreach (MemberInfo mi in t.GetMembers(bf)) {
               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);
   }
}

This is just a small section of the code’s output:

Assembly: mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
   Type: System.Object
      MethodInfo: System.String ToString()
      MethodInfo: Boolean Equals(System.Object)
      MethodInfo: Boolean InternalEquals(System.Object, System.Object)
      MethodInfo: Boolean Equals(System.Object, System.Object)
      MethodInfo: Boolean ReferenceEquals(System.Object, System.Object)
      MethodInfo: Int32 GetHashCode()
      MethodInfo: Int32 InternalGetHashCode(System.Object)
      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.ICloneable
      MethodInfo: System.Object Clone()
   Type: System.Collections.IEnumerable
      MethodInfo: System.Collections.IEnumerator GetEnumerator()
   Type: System.Collections.ICollection
      MethodInfo: Void CopyTo(System.Array, Int32)
      MethodInfo: Int32 get_Count()
      MethodInfo: System.Object get_SyncRoot()
      MethodInfo: Boolean get_IsSynchronized()
      PropertyInfo: Int32 Count
      PropertyInfo: System.Object SyncRoot
      PropertyInfo: Boolean IsSynchronized
   Type: System.Collections.IList
      MethodInfo: System.Object get_Item(Int32)
      MethodInfo: Void set_Item(Int32, System.Object)
      MethodInfo: Int32 Add(System.Object)
      MethodInfo: Boolean Contains(System.Object)
      MethodInfo: Void Clear()
      MethodInfo: Boolean get_IsReadOnly()
      MethodInfo: Boolean get_IsFixedSize()
      MethodInfo: Int32 IndexOf(System.Object)
      MethodInfo: Void Insert(Int32, System.Object)
      MethodInfo: Void Remove(System.Object)
      MethodInfo: Void RemoveAt(Int32)
      PropertyInfo: System.Object Item [Int32]

Using the BindingFlags

The BindingFlags enumeration is used to control how members of a type are retrieved using the GetMembers method. The BindingFlags enumeration is a flagged enumeration, which means that more than one value can be used. Below is a list of members of the BindingFlags enumeration:

  • DeclaredOnly: members directly declared on the specific type are included. Inherited members are ignored.
  • Default: no binding flags are used.
  • FlattenHierarchy: declared, inherited, and protected members are returned.
  • IgnoreCase: a case-sensitive matching of the member name should be used.
  • Instance: members that are part of an instance type will be included.
  • NonPublic: members that are not public are included.
  • Public: members that are public are included.
  • Static: members defined as static are included.

Here is the code that illustrates the use of BindingFlags:

C#
using System;
using System.Reflection;
public sealed class Program {
    public static void Main() {
        Type t = typeof(String);
        BindingFlags flags = BindingFlags.Public | 
                     BindingFlags.NonPublic | BindingFlags.Instance;
        MemberInfo[]  members = t.GetMembers(flags);
        foreach (MemberInfo member in members)
        {
            Console.WriteLine("Member:   {0}",  member.Name);
        }
    }
}

Output

Member:   get_FirstChar
Member:   Equals
Member:   Equals
Member:   Equals
Member:   get_Chars
Member:   CopyTo
Member:   ToCharArray
Member:   ToCharArray
Member:   GetHashCode
Member:   get_Length
Member:   get_ArrayLength
Member:   get_Capacity
Member:   Split
Member:   Split
Member:   Split
Member:   Split
Member:   Split
Member:   Split
Member:   InternalSplitKeepEmptyEntries
Member:   InternalSplitOmitEmptyEntries
Member:   MakeSeparatorList
Member:   MakeSeparatorList
Member:   Substring
Member:   Substring
Member:   InternalSubStringWithChecks
Member:   InternalSubString
Member:   Trim
Member:   TrimStart
Member:   TrimEnd
Member:   ConvertToAnsi_BestFit_Throw
Member:   IsNormalized
Member:   IsNormalized
Member:   Normalize
Member:   Normalize
Member:   CtorCharArray
Member:   CtorCharArrayStartLength
Member:   CtorCharCount
Member:   CtorCharPtr
Member:   CtorCharPtrStartLength
Member:   CompareTo
Member:   CompareTo
Member:   Contains
Member:   EndsWith
Member:   EndsWith
Member:   EndsWith
Member:   EndsWith
Member:   IndexOf
Member:   IndexOf
Member:   IndexOf
Member:   IndexOfAny
Member:   IndexOfAny
Member:   IndexOfAny
Member:   IndexOf
Member:   IndexOf
Member:   IndexOf
Member:   IndexOf
Member:   IndexOf
Member:   IndexOf
Member:   LastIndexOf
Member:   LastIndexOf
Member:   LastIndexOf
Member:   LastIndexOfAny
Member:   LastIndexOfAny
Member:   LastIndexOfAny
Member:   LastIndexOf
Member:   LastIndexOf
Member:   LastIndexOf
Member:   LastIndexOf
Member:   LastIndexOf
Member:   LastIndexOf
Member:   PadLeft
Member:   PadLeft
Member:   PadRight
Member:   PadRight
Member:   PadHelper
Member:   StartsWith
Member:   StartsWith
Member:   StartsWith
Member:   ToLower
Member:   ToLower
Member:   ToLowerInvariant
Member:   ToUpper
Member:   ToUpper
Member:   ToUpperInvariant
Member:   ToString
Member:   ToString
Member:   Clone
Member:   Trim
Member:   TrimHelper
Member:   Insert
Member:   Replace
Member:   Replace
Member:   Remove
Member:   Remove
Member:   GetTypeCode
Member:   System.IConvertible.ToBoolean
Member:   System.IConvertible.ToChar
Member:   System.IConvertible.ToSByte
Member:   System.IConvertible.ToByte
Member:   System.IConvertible.ToInt16
Member:   System.IConvertible.ToUInt16
Member:   System.IConvertible.ToInt32
Member:   System.IConvertible.ToUInt32
Member:   System.IConvertible.ToInt64
Member:   System.IConvertible.ToUInt64
Member:   System.IConvertible.ToSingle
Member:   System.IConvertible.ToDouble
Member:   System.IConvertible.ToDecimal
Member:   System.IConvertible.ToDateTime
Member:   System.IConvertible.ToType
Member:   IsFastSort
Member:   IsAscii
Member:   SetChar
Member:   AppendInPlace
Member:   AppendInPlace
Member:   AppendInPlace
Member:   AppendInPlace
Member:   AppendInPlace
Member:   AppendInPlace
Member:   ReplaceCharInPlace
Member:   NullTerminate
Member:   ClearPostNullChar
Member:   SetLength
Member:   GetEnumerator
Member:   System.Collections.Generic.IEnumerable<system.char>.GetEnumerator
Member:   System.Collections.IEnumerable.GetEnumerator
Member:   InternalSetCharNoBoundsCheck
Member:   InsertInPlace
Member:   InsertInPlace
Member:   RemoveInPlace
Member:   GetType
Member:   Finalize
Member:   MemberwiseClone
Member:   .ctor
Member:   .ctor
Member:   .ctor
Member:   .ctor
Member:   .ctor
Member:   .ctor
Member:   .ctor
Member:   .ctor
Member:   FirstChar
Member:   Chars
Member:   Length
Member:   ArrayLength
Member:   Capacity
Member:   m_arrayLength
Member:   m_stringLength
Member:   m_firstChar

The Type and MemberInfo-derived objects require a lot of memory. This means that if an application holds too many of these objects to later search that collection and invoke that object, the process' (that represents the instance of that application) working set will start to grow. That is, memory will be allocated for these objects that could well exceed an amount that the system has already predetermined. When you are saving/caching a lot of Type and MemberInfo-derived objects, you can reduce the size of the working set by using runtime handles instead of objects. The FCL defines three runtime handle types: RuntimeTypeHandle, RuntimeFieldHandle, and RuntimeMethodHandle. All of these types are value types that contain just one field: an IntPtr. The IntPtr field is a handle that refers to a type, field, or method in an AppDomain’s loader heap. So, the idea is to convert a heavyweight Type/MemberInfo object into a lightweight runtime handle instance, and vice versa. The referenced code below requires a lot of MemberInfo objects, converts them to RuntimeMethodHandle instances, and shows the working set difference:

C#
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:###,###}", methodInfos.Count);
      Show("After building cache of MethodInfo objects");


      // Build cache of RuntimeMethodHandles for all MethodInfo objects
      List<runtimemethodhandle> methodHandles = 
         methodInfos.ConvertAll<runtimemethodhandle>(
            delegate(MethodBase mb) { return 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>(
         delegate(RuntimeMethodHandle rmh) { 
            return 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");
   }

   private static void Show(String s) {
      Console.WriteLine("Heap size={0,12:##,###,###} - {1}", 
         GC.GetTotalMemory(true), s);
   }
}

Output:

Heap size=      13,564 - Before doing anything
# of methods=45,098
Heap size=   3,252,080 - After building cache of MethodInfo objects
Heap size=   3,432,556 - Holding MethodInfo and RuntimeMethodHandle cache
Heap size=     230,204 - After freeing MethodInfo objects
Heap size=   1,321,004 - Size of heap after re-creating MethodInfo objects
Heap size=      49,816 - After freeing MethodInfos and RuntimeMethodHandles

The format of this article focused on detecting information about assemblies and types. It is important to know that you can also define information at runtime and even create assemblies for in-memory consumption or for serialization to disk for later reuse.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer Monroe Community
United States United States
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
GeneralMy vote of 5 Pin
Michael Haephrati22-Feb-13 23:42
professionalMichael Haephrati22-Feb-13 23:42 
SuggestionRegarding .Net Certification? Pin
verriabbayi12-Jul-12 4:11
verriabbayi12-Jul-12 4:11 
Generalhiiiiiiiiii Pin
glory44415-Apr-11 8:50
glory44415-Apr-11 8:50 
GeneralGood Article Pin
arvindjo11-Aug-09 23:36
arvindjo11-Aug-09 23:36 
Generalwhy not a 5... Pin
mintxelas10-Aug-09 1:15
mintxelas10-Aug-09 1:15 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.