Introduction
The .NET reflection provides developers with complete tools to discover and, furthemore, to construct an executable code at run time. There are many articles on codeproject.com dedicated to these tools. We will recall some of their important features and we will demonstrate a practical example of using them.
We will explore some class library and select a collection of the specialized methods wich we need to run in separated threads. The custom attribute will help us to identify the proper methods and to extend the application's GUI.
Background
Some times ago I have published on codeproject.com the article "Covering a BackgroundWorker by an Own Class to Lighten the Creation of a Multithread Application" about execution of different sorting algorithms in parallel threads. The described application has a defect: the set of algorithms is hard-coded in the program and an user can not change it. This defect troubles me till now, thus I decided to fix it by dynamic loading of proper methods at run time. Interestingly, such approach makes it possible to reduce dependences in the application and to improve its architecture.
Reflection tools
The process of examination at run time of a compiled code is known as reflection. In the .NET world we can pick and investigate any exe or dll programmatically, obtain detailed information about their classes, dynamically instantiate these classes and execute their code, etc. Namespace System.Reflection
contains a set of classes that serve for these purposes. I'll recall some of them that are needed to solve my task.
Assembly loading
Suppose we need TheImportantClass
for our application, we have several assemblies with the class implementation produced by different manufacturers and we don't know at design time wich assembly we will prefer. Can we do the choice of assembly at run time? Yes we can! It is possible to determine the name of the assembly at run time and to load it dynamically.
Look at the code snippet below.
uses System.Reflection
public void LoadAssembly()
{
string assemblyName = myApplication.GetAssemblyName();
try
{
Assembly dynamicAssembly = Assembly.Load(assemblyName);
}
catch (System.IO.FileNotFoundException ex)
{
System.Windows.Forms.MessageBox.Show(ex.Message, "Assembly error",
MessageBoxButtons.OK, MessageBoxIcon.Error);
return;
}
string nameOfNamespace = assemblyName;
System.Type importantClassType = dynamicAssembly.GetType(nameOfNamespace + ".TheImportantClass");
Static method Assembly.Load()
takes a name of the assembly - the file name without any extension - and looks for the assembly in the application local folder and than in the global cache of assemblies.
Use static method Assembly.LoadFrom()
to determine the full path to the assembly:
string assemblyPath = myApplication.GetAssemblyPath();
Assembly dynamicAssembly = Assebly.Load(assemblyName);
string nameOfNamespace = System.IO.Path.GetFileNameWithoutExtension(assemblyPath);
System.Type importantClassType = dynamicAssembly.GetType(nameOfNamespace + ".TheImportantClass");
Type examination
When the assembly is loaded we get the instance of Assembly
wich helps us to discover types in the assembly. We can get a certain type if we know its name (like in the snippets below):
Type aClass = dynamicAssembly.GetType(theQualifiedNameOfClass);
Or can get an array of all types defined in the assembly:
Туре[] classes = dynamicAssembly.GetTypes();
foreach (Type definedType in classes)
{
myApplication.DoSomethingWith(definedType);
}
Every instance of System.Type
is a key to reflection of the corresponding class. It provides a set of properties to explore the class characteristics: IsAbstract
, IsEnum
, IsSealed
etc. It also provides a set of methods in singular and plural forms, like Assembly.GetType(typeName)
and Assembly.GetTypes()
, in order to get any member of the class. For example, we can use Type.GetProperty(propName)
to get a single PropertyInfo
object or Type.GetProperties()
to retrieve an array PropertyInfo[]
. Therefore it becomes possible to analyze either one specific property of the class or all the properies.
The PropertyInfo
object is useful to get (or set) a value of the corresponding property of any instance of the discovered type. Just call the method aPropertyInfo.GetValue(instanceOfDiscoveredType)
.
In order to solve my task I need to get an information about all sorting methods in the class. So I will use Type.GetMethods()
and analyze the received array MethodInfo[]
. The MethodInfo
instance provides us with the complete information about the method. We can use a wide range of properties (such as IsAbstract
, IsConstructor
, IsPublic
, IsStatic
, Name
, ReturnType
etc) to examine it and methods (such as CreateDelegate()
, GetCustomAttributes()
, GetParameters()
, Invoke()
etc) to manipulate it.
How can I separate the proper sorting methods from all the methods defined in the class? In other words, how can I select corresponding elements of the array MethodInfo[]
? The method that is able to sort an array of integers in a background thread (in my application) takes three arguments of certain types: int[]
, BackgroundWorker
and DoWorkEventArgs
. We can get the detailed description of the method's parameters as an array ParameterInfo[]
by GetParameters()
method. Then we can check the quantity of parameters, their types and so on:
List<MethodInfo> methods = new List<MethodInfo>();
foreach (MethodInfo method in sortMethodProviderType.GetMethods())
{
parms = method.GetParameters();
if (parms.Length == 3 && parms[0].ParameterType == typeof(int[]) &&
parms[1].ParameterType == typeof(BackgroundWorker) &&
parms[2].ParameterType == typeof(DoWorkEventArgs))
{
methods.Add(method);
}
}
The MethodInfo.Name
property can be used to display the selected methods in the GUI:
foreach (MethodInfo method in methods)
{
methodComboBox.Items.Add(method.Name);
}
In order to start any method we can use the method MethodInfo.Invoke(theMethodOwner,Parameters)
:
selectedMethod.Invoke(null, new object[] { arrayToSort, actualBackgroundWorker, eventArgs });
So far, I have described a possible but not the best way to select the required methods and to construct the GUI. If the application's code hasn't a reference to the assebly where the parameters types are defined, then the method recognition by type comparison becomes impossible. If we use code names of the methods in the application combobox (or listbox) then the GUI becomes unclear. We cope with these difficulties by code attributes described below.
Custom attributes
Attributes are special objects that allow us to decorate the code with additional meta-data. There are many attributes defined in the .NET namespaces: CLSCompliantAttribute
, ObsoleteAttribute
, SerializableAttribute
, TestClassAttribute
and more and more. The compiler, a testing framework or another program reads the attributes in order to make a decision on how to work with our code. For example, when the compiler reads [CLSCompliant] attribute before a class definition, it will check if each public member of this class is compatible with CLS. The BinaryFormatter.Serialize()
method is looking for the [Serializable] and so on. If a class has an attribute, then the attribute type signals about certain "property" of the class. Thus, the type of an attribute is the most important. Furthemore, an attribute can provide any additional information in its properties. For example, [Obsolete("The Warning Message")] attribute forces the compiler to add the warning with text "The Warning Message" to the Error List of the IDE.
For our purpose we can use a proper predefined attribute or define our own one. A custom attribute class must inherit the System.Attribute
class and its name must terminate with the suffix "Attribute". An attribute incapsulates data in its property wich must be set up by the constructor. We can define additional rules of usage of the attribute by the "attribute of attribute" [AttributeUsage]
: we can specify target of the attribute, restrict the multiple usage of it and so on.
To mark the proper sorting methods I used the MethodNameAttribute
described below:
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false)]
public sealed class MethodNameAttribute: System.Attribute
{
public string OuterName { get; set; }
public string LocalName { get; set; }
public MethodNameAttribute(string name)
{
OuterName = name;
}
}
The usage of the attribute looks like this:
[MethodName("Bubble sort", LocalName = "Метод бульбашки")]
public static void BubbleSortInBackground(int[] arrayToSort, BackgroundWorker worker,
DoWorkEventArgs e)
{
for ...
It is not necessary to write a full name of the attribute - the compiler adds suffix "Attribute" itself. The constructor of MethodNameAttribute
takes a string argument to initialize the property OuterName
. If we want to set up an optional property of the attribute, then we have to use the special syntax "property = value", as in the code above.
Reflection in action
Let me recall the goal: to expand functionality of the application with ability to load a set of sorting methods at run time. I did several steps to achieve this goal.
First. I don't know the name of an assembly containing definition of sorting methods. I will get it as a result of an OpenFileDialog
execution. But I am sure that the correct assembly contains classes SortMethodProvider
and MethodNameAttribute
. I will check this.
public bool LoadAssembly(string assemblyName)
{
...
System.Type sortMethodProviderType = assembly.GetType(nameOfNamespace + ".SortMethodProvider");
System.Type methodNameAttrType = assembly.GetType(nameOfNamespace + ".MethodNameAttribute");
if (sortMethodProviderType == null || methodNameAttrType == null)
{
MessageBox.Show(
"SortMethodProvider or MethodNameAttribute not found in " + assemblyName + " assembly",
"Sorting assembly error", MessageBoxButtons.OK, MessageBoxIcon.Error);
return false;
} ...
}
Second. I will select proper sorting method from all SortMethodProvider
's methods which are static and marked by the MethodNameAttribute
. One of the easiest way to execute the selection uses LINQ:
MethodInfo[] methods = (from m in sortMethodProviderType.GetMethods()
where (m.IsStatic && m.IsDefined(methodNameAttrType, false))
select m).ToArray<MethodInfo>();
Third. I will read the UI name of the method from the property of its attribute:
string[] methodNames = new string[methods.Length];
PropertyInfo propInfo = methodNameAttrType.GetProperty("OuterName");
for (int i = 0; i < methods.Length; ++i)
methodNames[i] = propInfo.GetValue(methods[i].GetCustomAttribute(methodNameAttrType)).ToString();
Fourth. I will create a new sorting thread and a new visual component in order to reflect the process of sorting dynamically on the user's request. (Don't forget to set Parent
property of the visual component created dynamically!)
private void btnAdd_Click(object sender, EventArgs e)
{
int[] array = model.GetArray();
ArrayView a = new ArrayView(new Point(xLocation[Views.Count], yLocation), array);
a.AddRange(controller.MethodNames);
a.ComboIndexChanged += arrayView_ComboIndexChanged;
a.SortingComplete += DecreaseThreadsRunning;
a.SetSorter(controller.GetSorter(array));
a.Parent = this;
Views.Add(a);
}
The previous version of the application was constructed according to the MVC pattern, but the Model holds a reference to the Controller to make some changes with the arrays. It is not very good. In the new version only the View communicate with the Model and with the Controller separately.
Conclusion
The .NET reflection tools are cool. They work good and are very useful to construct flexible applications.
Get fun with the new version of "Sorting in Threads" application!