Reflection

Reflection is a technology built into the .NET Framework that allows us to write code that can examine and interact with other code. We can use reflection to get a list of all the classes in an assembly, and all the methods or properties on each class. We can use reflection to get a list of all the instance variables in an object, and then we can use reflection to retrieve or alter the values of those variables .

Reflection is very powerful ”so powerful, in fact, that it's incredibly dangerous. Reflection can be used to circumvent application designs, to break object encapsulation, and generally to misuse virtually any bit of code or data in an application. For all its danger, however, reflection is also very useful. It can be used to implement systems that would be virtually impossible without it. We'll be making use of reflection in several places as we build the framework in Chapters 4 and 5; in this chapter, we'll just take a quick look at how it's used in general.

Working with Types

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 a Type Object

The .NET Framework includes a class named System.Type , which represents type information about any type. We can use the typeof function to retrieve the Type object representing a specific data type as follows :

  Type typeData = typeof(string);  

or

  Type typeData = typeof(Customer);  

Alternatively, we can get type information for a specific object by calling the GetType() method on that object as shown here:

  Type typeData = objCustomer.GetType();  

System.Object provides the GetType() method, and because all objects in .NET ultimately flow from System.Object , they too have a GetType() method that returns the type information about the object.

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.



Expert C# Business Objects
Expert C# 2008 Business Objects
ISBN: 1430210192
EAN: 2147483647
Year: 2006
Pages: 111

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net