Before we get into reflection itself, we need to remind ourselves that everything in .NET has a type, and that all types ultimately come from the base type, System.Object . This includes simple value types such as int or float , more complex types such as string or System.DateTime , and other types such as an enum or struct . It also includes any types that we may create using classes.
Getting Information from a Type Object
Once we have a Type object that represents the type data for either a data type or an object, we can use reflection to get information from that object. For example, we can get a list of the methods, properties, or fields (variables) contained within that type:
public class Customer { Guid _id = Guid.NewGuid(); string _name; float _sales; public Guid ID { get { return _id; } } public string Name { get { return _name; } set { _name = value; } } public float Sales { get { return _sales; } } public void Buy(int Quantity) { _sales += Quantity * (float)1.99; } }
This is a pretty basic class, but it has the important elements we're interested in: some variables (fields), some properties (read-only and read-write), and a method. Given an object of this type, we can get hold of a Type object that contains information about it, and then use the facilities of the System.Reflection namespace to retrieve that information. For example, we can get a list of all the public members (methods, properties, events, and variables) of our object as follows:
Customer cust = new Customer(); Type typeInfo = cust.GetType(); MemberInfo[] members = typeInfo.GetMembers(); foreach(MemberInfo Member in members) { System.Diagnostics.Debug.WriteLine(Member.Name); }
First, we get a Type object for our Customer object, and then we call the Type object's GetMembers() method. This returns an array of MemberInfo objects, each corresponding to one of the public members on our object. With that data in hand, we can loop through the array to find information about each member in turn . (In this case, we're printing each member's name to the debug window.)
The Type object has other methods for retrieving only method, property, or field information, but perhaps more importantly we can return not only the public members of an object, but its private , internal , or protected members as well. For instance, we can get a list of the private fields and their values from our object with code such as the following:
FieldInfo[] fields = typeInfo.GetFields(BindingFlags.Instance BindingFlags.NonPublic); foreach (FieldInfo field in fields) { System.Diagnostics.Debug.WriteLine( field.Name + ": " + field.GetValue(cust).ToString()); }
Notice that we're passing some values to the GetFields() method to indicate that we want only instance variables that aren't public . Once we get the array of non public variables, we can loop through them to display their names . We can also call the GetValue() method on the FieldInfo object, passing it our object reference, in order to retrieve the value in the variable. The result in the debug window will be something like this:
_id: 60a09fc2-b0c1-4e87-b175-5e499c02a973 _name: Fred _sales: 0
At this point, it's probably apparent just how dangerous reflection can be. Using this technique, we can peek inside any object to see its private data! Even more dangerous is that we can use a similar technique to reach in and alter data. For instance, we can directly change the _sales variable:
FieldInfo sales = typeInfo.GetField("_sales", BindingFlags.Instance BindingFlags.NonPublic); sales.SetValue(cust, 42f);
We first use the GetField() method to retrieve a FieldInfo object representing the private field _sales . The SetValue() method then allows us to change the value of that variable by providing a reference to the Customer object, and the new value.
Along this same line, we can use reflection to invoke methods both public and non public . Doing so is much slower than just calling a method normally, but we'll find a use for this technique in Chapter 5, when we implement a data service that works with any object. To call a method via reflection, we can write code such as the following:
MethodInfo method = typeInfo.GetMethod("Buy"); Object[] parameters = {5}; method.Invoke(cust, parameters);
First, the method information is retrieved into a MethodInfo object. (If required, we could use flags here to indicate that we want to get at a non public method.) Once we have the MethodInfo object, we can call its Invoke() method to invoke the method. We provide a reference to our object, and an array of type Object that contains the parameters to be passed to the method as it's invoked.
At this point, we've seen how we can get information about a type or an object, and then interact with it by altering its variables and calling its methods ”whether they're public or not. There's one more trick we should cover here. Normally, we can only create objects from a class that has a public constructor. If we don't write a constructor in a class, the compiler creates an empty constructor on our behalf . On the other hand, if we create a private constructor we can prevent the creation of an object.
Suppose that we add the following method to our Customer class:
public class Customer { Guid _id = Guid.NewGuid(); string _name = ""; float _sales = 0.0f; private Customer() { }
All of a sudden, our client code will fail to compile, due to the fact that we can no longer create an instance of the Customer class. This line will result in the following compilation error:
Customer cust = new Customer();
However, the .NET Framework provides a back door that we can use to create an instance of the class even with a private constructor . We can write the following:
Customer cust; cust = (Customer)Activator.CreateInstance(typeof(Customer), true);
This will create an instance of the object by calling its private constructor, but note that it only works if there's a default constructor ”that is, a constructor that requires no parameters. However, it can be used to create instances of an object where the new keyword would fail to compile.
We'll be using this technique as we build our framework, because it allows us to create our business objects so that the UI developer is forced to use static factory methods to create them. However, we still need to be able to create them from our DataPortal code, thereby bypassing the restrictions we're placing on the UI developer. Using reflection in this way allows us to do just that.