C# Reflection Programming: A Simple Example Concerning Reflection
What Will I Learn?
This series of tutorials will serve as only an overview of reflection – to master the more intensive contents concerning reflection requires your lots of practice.
Requirements
Visual Studio Code
Difficulty
Intermediate
Tutorial Contents
- A Simple Example Concerning Reflection
- Reflection Overview and the Type Class
- The role of reflection
- Get the instance of the Type object
- Type and the Reflection namespace organization
- Reflect Assembly
- Load assembly
- Reflect assembly info
C# Reflection Programming: A Simple Example Concerning Reflection
One of the powerful weapons .Net provides us is reflection. Although in many cases we may not use reflection and therefore we may not need to master it, but the understanding with reflection will surely help you much in the future development.In fact, reflection covers a huge topic, which involves a lot of knowledge points, including Assembly, custom Attributes, Generics, etc. It is very difficult to fully grasp it. Therefore, this series of articles will serve as only an overview of reflection – to master the more intensive contents concerning reflection requires your lots of practice...
A Simple Example Concerning Reflection
Whether Visual Studio Intelligence or the Refactoring function, they both leverage the reflection feature. And also, at .Net FCL (Framework Class Library), you are surely often seeing the shadow of reflection. Below I'll show you one of the most common examples.As you know, there are total two types in CLR: one is a value type; the other is a reference type. If you declare a reference type variable and instantiate it in the application, the system will in the application heap allocate related memory, create the object instance, and then return the memory address of the instance to the variable. So, you see, the variable holds the memory address, which is equivalent to a pointer in C++. Declare a variable of a value type, however, will result in allocating it in the thread stack. In this case, the variable itself contains all the fields of the value type.
Now suppose that we need to compare whether two objects are equal. When we compare two reference typed variables, what we compare is in fact whether these two variables point to the same instance in the heap (whether the memory address is the same or not). And when we compare two variables of value type, what shall we do? Because the variable itself contains all types of fields (data), during the course of comparison we need make a field-for-field comparison with these two variables to see whether the value of each field is equal. If the value of any field is different, then we conclude these two variables are not equal and return false.
In fact, implementing such a comparison does not need to write our own code, Microsoft has done all for us. As you know, all value types inherit from System.ValueType. ValueType and all types in turn inherit from System.Object. Object offers a Equals() method, used to determine whether two objects are equal. Please do bear in mind that ValueType overrides the Equals() method of Object. When we compare whether two value typed variables are equal, we can call the Equals() method inherited from the ValueType type.
Now, let's consider the following case.
1. Compare whether two struct typed variables are equal
public struct ValPoint {
public int x;
public int y;
}
static void Main(string[] args) {
bool result;
ValPoint A1;
A1.x = A1.y = 3;
ValPoint B1=A1; //copy the value of A to B
result = A1.Equals(B1);
Console.WriteLine(result); //output True;
}
What would happen when you call the Equals() method above? As already mentioned above, if two variables are both of a value type, the system will make a field-by-field comparison with the two variables, to see whether the value of each field is equal. But how to get all the variable related fields, traverse them, and make comparison one by one?At this point, you should be aware it is time to fall back upon reflection technique. To gain a clearer understanding, let us pick up the great tool - .NET Reflector to look into the Equals() method of class ValueType to see how Microsoft do with it.
2. The internal implementation of the Equals() method
public override bool Equals(object obj){
if (obj == null) {
return false;
}
RuntimeType type = (RuntimeType)base.GetType();
RuntimeType type2 = (RuntimeType)obj.GetType();
if (type2 != type) {
return false;
}
object a = this;
if (CanCompareBits(this)) {
return FastEqualsCheck(a, obj);
}
//obtain all the fields of the instance
FieldInfo[] fields = type.GetFields(BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance);
//traverse all the fields to judge whether they are equal
for (int i = 0; i < fields.Length; i++) {
object obj3 = ((RtFieldInfo)fields[i]).InternalGetValue(a, false);
object obj4 = ((RtFieldInfo)fields[i]).InternalGetValue(obj, false);
if (obj3 == null) {
if (obj4 != null) {
return false;
}
} else if (!obj3.Equals(obj4)) {
return false;
}
}
return true;
}
Till now, you should have noticed from the above two points that have been added comments, when comparing the value typed variables, it will use reflection to help. Of course, there exists poor performance in reflection. In other word, to call the Equals() method towards the value type will be very expensive. Nevertheless, this example simply illustrates the use of reflection. If you can not fully catch on the above code it does not matter, the latter contents will help you.
Up till now, you should have a preliminary concept of reflection: it is a broad term, which through the System.Reflection namespace together with the System.Type class allows you to discover and use types and members at runtime that you did not know about at compile time.
Reflection Overview and the Type Class
Starting from this section, we are going to use reflection to obtain the basic types associated information.
The role of reflection
In short, reflection provides the following capabilities:
- View and traverse the information of the basic types (and their members) and the metadata inside assemblies;
- Late bind methods and properties;
- Dynamically create type instances (and can dynamically invoke the methods, fields, properties in created instances).
The previous example only illustrates one usage of reflection - view members related information of some specified type. In the next few sections, we will gradually introduce other capabilities provided by reflection.
Get the instance of the Type object
By digging further, you will conclude the Type class plays the core role in .NET reflection, which not only encapsulates information about the objects, but also is the entrance of reflection. As soon as you get the Type object of a specified type, you can acquire all the information (methods, fields, properties, events, parameters, constructors, etc.) of this type using the properties and methods provided by the Type object.To do this, the first step is to get the Type instance of a specified type. On the whole, there are two forms to get the Type object: one is to obtain the type from inside the currently loaded assembly (Runtime); the other is from the unloaded one.Let's first consider the Runtime related Type. In general, there are three approaches:(1) Use the static method GetType() provided by the Type classFor example, if we want to get an instance of Type of the Stream type, we can do like this:
3. Get an instance of the Type instance of the Stream class
Type t = Type.GetType("System.IO.Stream");
txtOutput.Text = t.ToString();
Note the GetType method accepts a string typed argument.(2) Use the typeof operatorYou can also use the typeof operator in C# to achieve your target.
Type t = typeof(System.IO.Stream);
This way looks a bit like a Generic: Stream is like a parameter of some type, passed to the typeof operator.(3) Obtain the Type object through instance of a type
String name = "Hello reflection!";
Type t = name.GetType();
It should be noted, when using this method, even though we obtain the Type object through the variable (instance), the Type object does not contain information associated with the specific object, the info still being the String type related information.
Type and the Reflection namespace organization
Until now, I have repeatedly mentioned the Type object encapsulates types of information. Then, what types of information are included in it? Suppose we now have an instance of a type called demo, about which we have no idea. However, through the following code we got a Type instance for it:
//Somewhere in front of this point demo has been instantiated
Type t = demo.GetType();
Now, what types of information do we expect included inside t?
(1) The basic information of the type of demoOn the whole, we are interested in the following data:
- What type does demo belong to (demo related type name)?
- What namespace does this type in?
- What are its base type and its mapping type in .Net runtime library?
- Is it a value type or reference type?
- Is it Public?
- Is it a type of enumeration, class, array, or interface?
- Is it a base type (such as int, double, etc.)?
- And more...
By searching into Type using .NET Reflector, you will notice Type is an abstract class defined in the namespace System.Type inside the assembly mscorlib. It provides plenty of properties, used to obtain the basic information of related type.
(2) The member information related type of demoBesides the above info, we may still show interests in the following:
- What fields does it contain?
- What properties and all related information does it contain?
- How many constructors and what are they?
- How many methods, which parameters do they contain, and what kinds of return values?
- What events does it contain?
- Which interfaces does it implement?
Take the first item for example. All the field related info is packaged together in another FiledInfo type.In the above list, there are similar structures like FiledInfo type, such as PropertyInfo, ConstructorInfo, MethodInfo, and EventInfo. As for the method's parameters, there will be the out, ref, params parameters, parameter types and other information. And also, besides so many types with the Info suffix, in the System.Reflection namespace, there is an important ParameterInfo type, with which to encapsulate the method parameter information.Finally, please note that Type and all types with the Info suffix are all inherited from the MemberInfo type, which provides access to the basic information of types.
public abstract class Type : MemberInfo, _Type, IReflect{...}
public abstract class MethodBase : MemberInfo, _MethodBase
{...}
public abstract class ConstructorInfo : MethodBase, _ConstructorInfo
{...}
public abstract class PropertyInfo : MemberInfo, _PropertyInfo
{...}
......
Now, please start up Visual Studio 2010, type the key work "Type", select it, and then press F12 to jump to the definition of the class Type. Overview the members of class Type, you will find that the properties and methods can be roughly divided into such groups:
IsXXXX, such as IsAbstract. This set of bool typed attributes is used to describe some info of the type. (The table above has listed some.)GetXXXX(), such as theGetField()which returns FieldInfo. This set of methods is used to obtain a member's information.GetXXXXs(), such asGetFields()which returns FieldInfo[]. This set of methods is used to obtain certain members' information.- Other properties and methods...
As MemberInfo is a base class, when we get an instance of MemberInfo, we do not know whether it is PropertyInfo (object encapsulates the attributes) or FieldInfo (object encapsulates the attributes). So, it is necessary to provide a way to enable us make judgment. In the System.Reflection namespace, you will encounter a lot of bit flags. One of the important enumerations is MemberTypes, which is used to mark the type of members. Detailed definition of MemberTypes is as follows.
[Serializable, ComVisible(true), Flags]
public enum MemberTypes (
Constructor = 1, // the member is a constructor
Event = 2, // the member is an event
Field = 4, // the member is a field
Method = 8, // the member is a method
Property = 0x10, // the member is a property
TypeInfo = 0x20, // the member is a type
Custom = 0x40, // custom member type
NestedType = 0x80, // the member is a nested type
All = 0xbf, // specify all member types.
)
Starting from next section, we are going to use reflection to obtain interested info inside a custom assembly.
Reflect Assembly
In .Net, the assembly is the basic unit for employment and version control, which contains the relevant modules and types. Here, I do not intend to explain in detail the assembly and its components, but dwell on how to get the assembly information via reflection.
Load assembly
In the System.Reflection namespace there is defined an Assembly class, which represents an assembly and includes information about the assembly.When the assembly is loaded in the program, there are a few ways. We can use the two static methods provided by Assembly: LoadFrom() and Load(). For example:
Assembly asm = Assembly.LoadFrom ("Demo.dll");
or:
Assembly asm = Assembly.Load ("Demo");
When using the LoadFrom() method, you can only supply the file name of the assembly (*.dll). But wait a minute; the prerequisite is you have referenced the assembly to the project. If you want to load an assembly belonging to the current project, you need to give the full path, such as:
Assembly asm1 = Assembly.LoadFrom(@"C:\WINDOWS\Microsoft.NET\Framework\v4.7.1\System.Web.dll");
When Using the Load() method, you can only provide the assembly name, without the suffix.If you want to get the current assembly, you can use the static method GetExecutingAssembly of class Assembly, which returns the current executing code related assembly (that is, the current assembly).
Assembly as = Assembly.GetExecutingAssembly();
NOTE
In most dynamically extensible applications, Assembly's Load method is the preferred way of loading an assembly into an AppDomain.Well, now as soon as we get an instance of Type, we can use the instance's Assembly attribute to get the assembly it belongs to:
Type t = typeof (int)
Assembly asm = t.Assembly;
Note an assembly may have multiple modules; each module may also contain many types. By default, however, .Net compiler only allows an assembly contains one module. Now, let's have a look at what abilities reflection provides us to obtain information about the assembly.
NOTE
There is no necessary relationship between the assembly and namespace: an assembly can contain multiple namespaces; the same namespace can be distributed in several assemblies.
Reflect assembly info
Let's construct a concrete example. First, start up Visual Studio and create a Windows console application named ReflectAssembly.Next, click the menu item "File | Add | New Project..." to add a new Class Library project named Demo to the solution. We will later write code to view the type information in the assembly or make type related late binding on it. To make things simple, the Demo project contains only one single namespace named Demo. Let's look at the detailed coding, as follows:
4. Stuff defined in the class library
namespace Demo
{
public abstract class BaseClass{}
public struct DemoStruct { }
public delegate void DemoDelegate(Object sender, EventArgs e);
public enum DemoEnum
{
terrible, bad, common = 4, good, wonderful = 8
}
public interface IDemoInterface
{
void SayGreeting(string name);
}
public interface IDemoInterface2 { }
public sealed class DemoClass : BaseClass, IDemoInterface, IDemoInterface2
{
private string name;
public string city;
public readonly string title;
public const string text = "Const Field";
public event DemoDelegate myEvent;
public string Name
{
private get { return name; }
set { name = value; }
}
public DemoClass()
{
title = "Readonly Field";
}
public class NestedClass { }
public void SayGreeting(string name)
{
Console.WriteLine("Morning :" + name);
}
}
}
In the above sample, we've defined a lot of types, including abstract class, delegate, enum, struct, derived class, and some simple types. Since they are very simple just for demonstration, we no more waste words to give detailed explanation.Now, let's write a client-side method named AssemblyExplore() to check out all the types defined in the above assembly Demo.dll:
5. The client-side code to examine the target assembly
//others omitted...
using System.Reflection;
namespace ReflectAssembly
{
class Program
{
public static void AssemblyExplore()
{
StringBuilder sb = new StringBuilder();
Assembly asm = Assembly.Load("Demo");
sb.Append("FullName: " + asm.FullName + "\n");
sb.Append("Location(path): " + asm.Location + "\n");
Type[] types = asm.GetTypes();
foreach (Type t in types)
{
sb.Append(" Type: " + t + "\n");
}
Console.WriteLine(sb.ToString());
}
static void Main(string[] args)
{
AssemblyExplore();
Console.ReadKey();
}
}
}
Here, we used the tools introduced previously to load the target assembly demo.dll and obtain all the types defined in it. Now, just press F5 to watch the running-time output, as shown in Figure 1 below.
Figure 1: Output for the assembly reflecting sample
Summary
This tutorial mainly addressed the main tools to implement .NET reflection support. In conclusion, we've only covered two types of commonly and typically used tools: the Type class and Assembly class related members. Finally, we created a simple sample to use reflection to retrieve the general info inside a custom assembly. Starting from the next article, we are going to examine more concrete and advanced samples using .NET reflection functionality.
Posted on Utopian.io - Rewarding Open Source Contributors