Search content within the blog

Sunday, May 8, 2011

Reflection in c# - part I

Wikipedia says that "In computer science, reflection is the process by which a computer program can observe and modify its own structure and behaviour". This is exactly how Reflection in C# works, and while you may not realize it at this point, being able to examine and change information about your application during runtime, offers huge potential. Reflection, which is both a general term, as well as the actual name of the reflection capabilities in C#, works very, very well, and it's actually not that hard to use. In the next couple of chapters, we will go more into depth about how it works and provide you with some cool examples, which should show you just how useful Reflection is.

However, to get you started and hopefully interested, here is a small example. It solves a question that I have seen from many newcomers to any programming language: How can I change the value of a variable during runtime, only by knowing its name? Have a look at this small demo application for a solution, and read the next chapters for an explanation of the different techniques used.

using System;
using System.Collections.Generic;
using System.Text;
using System.Reflection;

namespace ReflectionTest
{
class Program
{
private static int a = 5, b = 10, c = 20;

static void Main(string[] args)
{
Console.WriteLine("a + b + c = " + (a + b + c));
Console.WriteLine("Please enter the name of the variable that you wish to change:");
string varName = Console.ReadLine();
Type t = typeof(Program);
FieldInfo fieldInfo = t.GetField(varName, BindingFlags.NonPublic | BindingFlags.Static);
if(fieldInfo != null)
{
Console.WriteLine("The current value of " + fieldInfo.Name + " is " + fieldInfo.GetValue(null) + ". You may enter a new value now:");
string newValue = Console.ReadLine();
int newInt;
if(int.TryParse(newValue, out newInt))
{
fieldInfo.SetValue(null, newInt);
Console.WriteLine("a + b + c = " + (a + b + c));
}
Console.ReadKey();
}
}
}
}

Try running the code and see how it works. Besides the lines where we use the actual Reflection, it's all very simple. Now, go to the next chapter for some more theory on how it works.



The right Type
The Type class is the foundation of Reflection. It serves as runtime information about an assembly, a module or a type. Fortunately, obtaining a reference to the Type of an object is very simply, since every class that inherits from the Object class, has a GetType() method. If you need information about a non-instantiated type, you may use the globally available typeof() method, which will do just that. Consider the following examples, where we use both approaches:

using System;
using System.Collections.Generic;
using System.Text;
using System.Reflection;

namespace ReflectionTest
{
class Program
{
static void Main(string[] args)
{
string test = "test";
Console.WriteLine(test.GetType().FullName);
Console.WriteLine(typeof(Int32).FullName);
Console.ReadKey();
}
}
}

We use the GetType() method on our own variable, and then we use the typeof() on a known class, the Int32. As you can see, the result in both cases is a Type object, for which we can read the FullName property.

At some point, you might even only have the name of the type that you're looking for. In that case, you will have to get a reference to it from the proper assembly. In the next example, we get a reference to the executing assembly, that is, the assembly from where the current code is being executed from, and then we list all of it's types:

using System;
using System.Collections.Generic;
using System.Text;
using System.Reflection;

namespace ReflectionTest
{
class Program
{
static void Main(string[] args)
{
Assembly assembly = Assembly.GetExecutingAssembly();
Type[] assemblyTypes = assembly.GetTypes();
foreach(Type t in assemblyTypes)
Console.WriteLine(t.Name);
Console.ReadKey();
}
}

class DummyClass
{
//Just here to make the output a tad less boring :)
}
}

The output will be the name of the two declared classes, Program and DummyClass, but in a more complex application, the list would probably be more interesting. In this case, we only get the name of the type, but obviously we would be able to do a lot more, with the Type reference that we get. In the next chapters, I will show you a bit more on what we can do with it.

Instantiating a class
So far, we have worked with .NET types or objects already instantiated. But with Reflection, we can actually do the instantiation at runtime as well, knowing the name of the class we wish to instantiate. There are several ways of doing it, but I prefer getting a reference to the constructor that I wish to use, invoke it, and then use the returned value as my instance. Here's an example of doing just that. Code first, then I will explain it all:

using System;
using System.Collections.Generic;
using System.Text;
using System.Reflection;

namespace ReflectionTest
{
class Program
{
static void Main(string[] args)
{
Type testType = typeof(TestClass);
ConstructorInfo ctor = testType.GetConstructor(System.Type.EmptyTypes);
if(ctor != null)
{
object instance = ctor.Invoke(null);
MethodInfo methodInfo = testType.GetMethod("TestMethod");
Console.WriteLine(methodInfo.Invoke(instance, new object[] { 10 }));
}
Console.ReadKey();
}
}

public class TestClass
{
private int testValue = 42;

public int TestMethod(int numberToAdd)
{
return this.testValue + numberToAdd;
}
}
}

I have defined a simple class for testing this, called TestClass. It simply contains a private field and a public method. The method returns the value of the private property, with the value of the parameter added to it. Now, what we want is to create a new instance of this TestClass, call the TestMethod and output the result to the console.

In this example, we have the luxury of being able to use the typeof() directly on the TestClass, but at some point, you may have to do it solely by using the name of the desired class. In that case, you can get a reference to it through the assembly where it is declared, as demonstrated in the chapter about Type.

So, with a Type reference to the class, we ask for the default constructor by using the GetConstructor() method, passing System.Type.EmptyTypes as a parameter. In case we wanted a specific constructor, we would have to provide an array of Type's, each defining which parameter the constructor we were looking for, would take.

Once we have a reference to the constructor, we simply call the Invoke() method to create a new instance of the TestClass class. We pass null as the parameter to Invoke(), since we're not looking to specify any parameters. We use the GetMethod(), along with the name of the method we want, to get the TestMethod() function, and then we once again use the magic of the Invoke() method to call this function. This time we need to specify a parameter, in the form of an array of objects. We do that on-the-fly, specifying the number 10 as the only parameter we need, and we then output the result of the method invocation. All of this through the magic of Reflection!


A Reflection based settings class
Okay, so I thought that I would end this part of the tutorial about Reflection, with a cool and useful example. It's a bit bigger than the usual examples here at the site, but hopefully you will find it really useful. It uses a bunch of the stuff that we have looked into during the last couple of chapters, so hopefully you can keep up.

A common scenario when creating any sort of application, is the desire to save the users settings. When you get several settings, you will probably create a Settings class, which will handle loading and saving of the desired settings. Each time you need a new setting in your Settings class, you will have to update the Load() and Save() methods, to include this new setting. But hey, why not let the Settings class discover its own properties and then load and save them automatically? With Reflection, it's quite easy, and if you have read the other chapters in the Reflection section of this tutorial, you should be able to understand the following example.

To make it fit better into a small example, I am saving information about a person instead of application settings, but hopefully you will get the general idea anyway. Please be aware that using Reflection WILL be slower than reading and writing known properties manually, so you should consider when to use it and when to opt for a faster approach! Also, in our example, we use a simple text file for storing even simpler values, only separated by a | (pipe character). If you're using this for real world stuff, you will probably want a better format for your data, perhaps XML. And of course, there is not much error handling, so you should probably add some of that as well.

Okay, let's get started. First, our Person class, which you can simply rename to Settings or something like that, to make it more useful to you:

public class Person
{
private int age = -1;
private string name = String.Empty;

public void Load()
{
if(File.Exists("settings.dat"))
{
Type type = this.GetType();

string propertyName, value;
string[] temp;
char[] splitChars = new char[] { '|' };
PropertyInfo propertyInfo;

string[] settings = File.ReadAllLines("settings.dat");
foreach(string s in settings)
{
temp = s.Split(splitChars);
if(temp.Length == 2)
{
propertyName = temp[0];
value = temp[1];
propertyInfo = type.GetProperty(propertyName);
if(propertyInfo != null)
this.SetProperty(propertyInfo, value);
}
}
}
}

public void Save()
{
Type type = this.GetType();
PropertyInfo[] properties = type.GetProperties();
TextWriter tw = new StreamWriter("settings.dat");
foreach(PropertyInfo propertyInfo in properties)
{
tw.WriteLine(propertyInfo.Name + "|" + propertyInfo.GetValue(this, null));
}
tw.Close();
}

public void SetProperty(PropertyInfo propertyInfo, object value)
{
switch(propertyInfo.PropertyType.Name)
{
case "Int32":
propertyInfo.SetValue(this, Convert.ToInt32(value), null);
break;
case "String":
propertyInfo.SetValue(this, value.ToString(), null);
break;
}
}

public int Age
{
get { return age; }
set { age = value; }
}

public string Name
{
get { return name; }
set { name = value; }
}
}

Okay, there's a lot of stuff, I know. But I will help you through the entire class. First of all, we have a couple of private fields, for holding information about our person. In the bottom of the class, we have the corresponding public properties which uses the private fields of course.

We also have a Load() method. It looks for the file settings.dat, and if it exists, it reads the entire file and places each line of it in an array of strings. Now, we run through each setting line, and splits it up into two parts: A property name and a value part. If both is present, we simply use the Type object to get the property with the property name, and then we set the value for it, using our own SetProperty method.

The SetProperty() method looks at the type of the property about to be changed, and then acts correspondingly. Right now, it only supports integers and strings, but as you probably realize, extending this support would be quite easy.

The Save() method gets an array of PropertyInfo instances, one for each of the defined properties on the Person class, and then uses a TextWriter to write each property, and its value, to the data file.

Now we just need some code to use this class. This small application will try to load the person from the settings file, and if it doesn't succeed, the user is prompted for the information:

class Program
{
static void Main(string[] args)
{
Person person = new Person();
person.Load();
if((person.Age > 0) && (person.Name != String.Empty))
{
Console.WriteLine("Hi " + person.Name + " - you are " + person.Age + " years old!");
}
else
{
Console.WriteLine("I don't seem to know much about you. Please enter the following information:");
Type type = typeof(Person);
PropertyInfo[] properties = type.GetProperties();
foreach(PropertyInfo propertyInfo in properties)
{
Console.WriteLine(propertyInfo.Name + ":");
person.SetProperty(propertyInfo, Console.ReadLine());
}
person.Save();
Console.WriteLine("Thank you! I have saved your information for next time.");
}
Console.ReadKey();
}
}

Everything here is pretty trivial, except for the part where we ask the user for information. Once again, we use Reflection, to get all the public properties of the Person class, and then ask for each of them.

No comments:

Post a Comment