Reflection is when managed code can read its own metadata to find assemblies. Essentially, it allows code to inspect other code within the same system. To illustrate, Java’s static typing system isn’t designed to support the “doSomething” method unless the object conforms to a known interface. But with reflection, your code can view the object and find out if it has the “doSomething” method. In addition, you can call it if necessary.
With reflection in C#, you can dynamically create an instance of a type and bind that type to an existing object. Moreover, you can get the type from an existing object and access its properties. When you use attributes in your code, reflection gives you access as it provides objects of Type that describe modules, assemblies, and types.
To understand reflection, there are a few basics you should understand about modules, types, and members:
You need to use Reflection when you want to inspect the contents of an assembly. For example, you can get all members of the object by typing “.” before an object when viewing your Visual Studio editor IntelliSense.
A program reflects on itself when it extracts metadata from its assemblies, then uses it to modify its own behavior or inform the user. You can compare Reflection to C++RTTI (Runtime Type Information), except that it has a much wider swath of capabilities. When you write a C# program that uses reflection, you can use either the TypeOf operator or the GetType() method to get the object’s type.
Reflection can be used to create applications called type browsers which allow users to select types and then read the data provided about them. This example illustrates how to use the static method GetType to find the Type of a variable:
// Using GetType to obtain type information: int i = 42; System.Type type = i.GetType(); System.Console.WriteLine(type);
The above example results in the following output:
System.Int32
Implementing reflection in C# requires a two-step process. You first get the “type” object, then use the type to browse members such as “methods” and “properties.”
This is how you would create instances of DateTime class from the system assembly:
// create instance of class DateTime DateTime dateTime = (DateTime)Activator.CreateInstance(typeof(DateTime));
To access the sample class Calculator from Test.dll assembly, the Calculator class should be defined as the following:
namespace Test { public class Calculator { public Calculator() { ... } private double _number; public double Number { get { ... } set { ... } } public void Clear() { ... } private void DoClear() { ... } public double Add(double number) { ... } public static double Pi { ... } public static double GetPi() { ... } } }
Then, you can use reflection to load the Test.dll assembly:
// dynamically load assembly from file Test.dll Assembly testAssembly = Assembly.LoadFile(@"c:\Test.dll");
To create an instance of the calculator class:
// get type of class Calculator from just loaded assembly Type calcType = testAssembly.GetType("Test.Calculator"); // create instance of class Calculator object calcInstance = Activator.CreateInstance(calcType);
And access its members (the following examples illustrate getting values for the public double Number property):
// get info about property: public double Number PropertyInfo numberPropertyInfo = calcType.GetProperty("Number"); // get value of property: public double Number double value = (double)numberPropertyInfo.GetValue(calcInstance, null); // set value of property: public double Number numberPropertyInfo.SetValue(calcInstance, 10.0, null);
The main class for reflection is the System.Type class, which is an abstract class representing a type in the Common Type System (CTS). When you use this class, you can find the types used in a module and namespace and also determine if a given type is a reference or value type. You can parse the corresponding metadata tables to look through these items:
Late bindings can also be achieved through reflection. To illustrate, you might not know which assembly to load during compile time. In this instance, you can ask the user to enter the assembly name and type during run time so the application can load the appropriate assembly. With the System.Reflection.Assembly type, you can get three static types which allow you to load an assembly directly:
When you consider that an assembly is a logical DLL or EXE and a manifest is a detailed overview of an assembly, then it makes sense that a PE (portable executable) file for CTS would have the extension of .dll or .exe. Within the PE file is mainly metadata, which contains a variety of different tables such as a:
When you parse these tables, you can retrieve an assembly’s types and attributes.
There are several uses including:
Other uses for Reflection include constructing symbol tables, to determine which fields to persist and through serialization.
For more information, including some helpful tutorials, visit the following resources:
At Stackify, we think C# and .NET Core will be big in 2018, so brush up on your C# skills by checking out some of our other resources on topics such as .NET logging best practices, understanding and profiling C# sync await tasks, how to find unhandled exceptions, how to convert a C# string to int, and more.
Stackify's APM tools are used by thousands of .NET, Java, PHP, Node.js, Python, & Ruby developers all over the world.
Explore Retrace's product features to learn more.
If you would like to be a guest contributor to the Stackify blog please reach out to [email protected]