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:
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:
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:
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);
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:
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) {
Assembly a = Assembly.Load(assemId);
foreach (Type t in a.GetExportedTypes()) {
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:
Assembly a = Assembly.GetExecutingAssembly();
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:
Module[] mods = a.GetModules();
Module m = mods[0];
Type[] moduleTypes = m.GetTypes();
You can create a Type
object using the C# typeof
keyword:
Type specficType = typeof(Int32);
Now that we can get a Type
object, we instantiate Type
to get information about the type, like so:
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:
using System;
using System.Reflection;
public sealed class Program {
public static void Main() {
Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies();
foreach (Assembly a in assemblies) {
Show(0, "Assembly: {0}", a);
foreach (Type t in a.GetExportedTypes()) {
Show(1, "Type: {0}", t);
const BindingFlags bf = BindingFlags.DeclaredOnly |
BindingFlags.NonPublic | BindingFlags.Public |
BindingFlags.Instance | BindingFlags.Static;
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
:
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:
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("Before doing anything");
List<methodbase> methodInfos = new List<methodbase>();
foreach (Type t in typeof(Object).Assembly.GetExportedTypes()) {
if (t.IsGenericTypeDefinition) continue;
MethodBase[] mb = t.GetMethods(c_bf);
methodInfos.AddRange(mb);
}
Console.WriteLine("# of methods={0:###,###}", methodInfos.Count);
Show("After building cache of MethodInfo objects");
List<runtimemethodhandle> methodHandles =
methodInfos.ConvertAll<runtimemethodhandle>(
delegate(MethodBase mb) { return mb.MethodHandle; });
Show("Holding MethodInfo and RuntimeMethodHandle cache");
GC.KeepAlive(methodInfos);
methodInfos = null;
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);
GC.KeepAlive(methodInfos);
methodHandles = null;
methodInfos = null;
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.