January 29, 2018 · .NET Programming Visual C# Visual Studio

Using Reflection to execute assemblies at runtime in C#

I recently was tasked with integrating some external DLL assemblies into Sage CRM. These external assemblies were generated by a Word plugin that lets you insert SQL fields directly into the Word document. Each document is then generated as an assembly which lets you pass parameters to it and it will generate a document with values from the database. In this case, documents were being generated regularly and, with the current C# solution we had, we had to reference the new document assembly in the solution every time, rebuild it and redeploy it. This was terribly inefficient and, because the each document’s assembly had exactly the same methods, a lot of code was copied and pasted for every new document making it very messy.

When I was tasked with updating this code to add validation and other features, I figured there had to be a neater way to do this. After some research, I discovered how Reflection could help me solve the problem.

What is Reflection?

Reflection allows you to inspect and use assemblies at runtime. This allows the application to load other assemblies and use them as well as inspect types and modules in referenced assemblies. These features are powerful because they allow for a lot of dynamic programming where assemblies and types do not have to be clearly defined when an application is built.

In this case, I had a list of DLL assemblies that would change as documents were edited and recompiled or as new documents were created. I couldn’t add them as references to my project because then I would have to rebuild the project every time I wanted to use a new document. This is a good example of where Reflection can be used to manage and use the list of assemblies that is constantly changing.

Introduction to our project

In this article, we are going to use Reflection to load an assembly at runtime, extract a few methods from it and then execute them. Our assembly that we will be loading will simple write some text that we specify to a file. This should then give you an idea of the capabilities of Reflection and how you can use it in bigger projects such as the one I used it in.

The ChordFileGenerator assembly that we will load

Before we begin with the actual Reflection, we will first create a basic DLL file that we will load using Reflection that will write some music chords, tabs or lyrics to a text file looking something like this:

Song Name: Our God
Artist: Chris Tomlin
Type: Chords
--------------------------------------------------
Verse 1:
Em        C          G       D
Water you turned into wine
Em         C           G
opened the eyes of the blind
...

We will have a class that will contain the methods and properties which we will then set in our code using Reflection. The code for this is contained in ChordFileGenerator.dll and looks as follows:

using System.Collections.Generic;
using System.IO;
using System.Text;
 
namespace ChordFileGenerator
{
    public class ChordFileGenerator
    {
        // The name of the song in the file
        public string SongName { get; set; }
        // The artist of the song
        public string Artist { get; set; }
        // The type of file it is: Chords, Tab or Lyrics
        public FileType FileType { get; set; }
 
        // Stores a list of lines to insert into the file
        private List<string> lines;
 
        // Creates a new file
        public ChordFileGenerator()
        {
            lines = new List<string>();
        }
 
        // Adds a single lyric line to the file
        public void AddLine(string lyrics)
        {
            lines.Add(lyrics);
        }
 
        // Adds a chord line and the corresponding lyric line
        public void AddLine(string chords, string lyrics)
        {
            AddLine(chords);
            AddLine(lyrics);
        }
 
        // Saves the file to a location that we specify
        public void SaveFile(string fileName)
        {
            // Get the contents of the file
            string contents = GenerateFileContents();
 
            // Save the file
            File.WriteAllText(fileName, contents);
        }
 
        private string GenerateFileContents()
        {
            StringBuilder contents = new StringBuilder();
 
            contents.AppendLine("Song Name: " + SongName);
            contents.AppendLine("Artist: " + Artist);
 
            switch (FileType)
            {
                case FileType.Chords:
                    contents.AppendLine("Type: Chords");
                    break;
                case FileType.Tab:
                    contents.AppendLine("Type: Tab");
                    break;
                default:
                    contents.AppendLine("Type: Lyrics");
                    break;
            }
 
            contents.AppendLine("--------------------------------------------------");
 
            foreach (string line in lines)
                contents.AppendLine(line);
 
            return contents.ToString();
        }
    }
 
    public enum FileType
    {
        Chords,
        Tab,
        Lyrics
    }
}

Using the assembly with Reflection

Now that we have created the DLL that we want to load, it’s time to load that file and use it using Reflection.

Loading the assembly

We first need to load the assembly from file. In this example, we will load the DLL using a file path to the DLL which will be stored in a subfolder that is in the same directory as the executable:

The file structure for this project

To load the assembly, we will use the Assembly.LoadFrom(string assemblyFile). To help us resolve the full path of the DLL we will use FileInfo to get the full path.

// Load the assembly. We are using FileInfo so that we can get the full
// path to the assembly if a relative path is specified.
FileInfo f = new FileInfo("assemblies\\ChordFileGenerator.dll");
Assembly assembly = Assembly.LoadFrom(f.FullName);

Getting the type to use

Next up we need to get the class (type) that we want to instantiate. In this case, it is the ChordFileGenerator class. To get this class, we will use the Assembly.GetType(string name) which takes the full name of the type (i.e. including the namespace) and returns it:

// Get the type from the assembly
Type t = assembly.GetType("ChordFileGenerator.ChordFileGenerator");

Getting and setting the properties

In order to access the properties, we must get a PropertyInfo object from the type for the specific property we want to access. This is done using the Type.GetProperty(string name). We can then use that PropertyInfo to access it.

To do this, we are going to write two methods, one to retrieve the value of any property we specify and one to set the value of a specified property. The methods will look as follows:

/// <summary>
/// This method retrieves the value of a particular property of an object
/// </summary>
/// <param name="o">The object to retrieve the property value from</param>
/// <param name="propertyName">The name of the property to retrieve</param>
/// <returns>The value of the property</returns>
public static object GetPropertyValue(object o, string propertyName)
{
    // Get the property we want to access
    PropertyInfo property = o.GetType().GetProperty(propertyName);
 
    // Retrieve the value of that property in the specified object o
    return property.GetValue(o);
}
 
/// <summary>
/// This method sets the property of a particular object
/// </summary>
/// <param name="o">The name of the object to set the property on</param>
/// <param name="propertyName">The name of the property to set</param>
/// <param name="value">The value to set the property to</param>
public static void SetPropertyValue(object o, string propertyName, object value)
{
    // Get the property we want to access
    PropertyInfo property = o.GetType().GetProperty(propertyName);
 
    // Set the value of the property for the specified object o
    property.SetValue(o, value);
}

Calling the methods

We will also need to be able to execute methods in the type that we have loaded. To do so, we must have the MethodInfo object for the method and then Invoke it with parameters that we specify. To get the MethodInfo for a method, we can use the Type.GetMethod(string name, Type[] types) which will let us specify the name of the method and the parameters it takes (so that we can accommodate for overridden methods).

To do this, we are also going to make a method that lets us easily retrieve and invoke a method by passing it the method name and arguments. Our code will look as follows:

/// <summary>
/// This method takes an object along with a method name and parameters and invokes the method
/// </summary>
/// <param name="o">The object to invoke the method on</param>
/// <param name="methodName">The name of the method to invoke</param>
/// <param name="arguments">The arguments to pass to the method</param>
/// <returns>The return value of the method</returns>
public static object InvokeMethod(object o, string methodName, object[] arguments)
{
    // First lets generate a Type[] for the parameters.  To do this we use some
    // LINQ to select the type of each element in the arguments array
    Type[] types = arguments.Select(x => x.GetType()).ToArray();
 
    // Get the MethodInfo for the method for the object specified
    MethodInfo method = o.GetType().GetMethod(methodName, types);
 
    // Invoke the method on the object we passed and return the result.
    return method.Invoke(o, arguments);
}

Accessing and using Enums

One more thing we need to consider is the enum that we are using for the FileType property. We need to be able to access it so that we can use it to assign a value to the property. To do this, we will build the following function which will get the corresponding value for a specified name in an enum:

/// <summary>
/// Gets the value of an enum item based on its name
/// </summary>
/// <param name="enumType">The enum to use</param>
/// <param name="enumItemName">The name of the item to get the value for</param>
/// <returns>The value of the specified item in the specified enum</returns>
public static object GetEnumValue(Type enumType, string enumItemName)
{
    // Get the underlying type used for each enum item
    Type enumUnderlyingType = enumType.GetEnumUnderlyingType();
 
    // Get a list of all the names in the enums
    List<string> enumNames = enumType.GetEnumNames().ToList();
    // Get an array of all the corresponding values in the enum
    Array enumValues = enumType.GetEnumValues();
 
    // Get the value where the corresponding name matches our specified name
    object enumValue = enumValues.GetValue(enumNames.IndexOf(enumItemName));
 
    // Convert the value to the underlying enum type and return it
    return Convert.ChangeType(enumValue, enumUnderlyingType);
}

Putting it all together

Now that we have written all these methods, we can use them to access and use the assembly in our own simple application.

Our application will take the name and artist of a song and then also a file type (chords, tab or lyrics). The user can enter lyrics or chord lines and lyrics. This will repeat until the user chooses to stop. The code to run this whole process looks as follows (we place the ChordFileGenerator.dll file in a subfolder called assemblies in our build folder for our program):

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
 
namespace LoadingAssembliesWithReflection
{
    class Program
    {
        static void Main(string[] args)
        {
            // Load the assembly. We are using FileInfo so that we can get the full
            // path to the assembly if a relative path is specified.
            FileInfo f = new FileInfo("assemblies\\ChordFileGenerator.dll");
            Assembly assembly = Assembly.LoadFrom(f.FullName);
 
            // Get the type from the assembly
            Type t = assembly.GetType("ChordFileGenerator.ChordFileGenerator");
 
            // Create a new instance of the ChordFileGenerator type
            object o = Activator.CreateInstance(t);
 
            // Get the song name and set the SongName property
            Console.Write("Song Name: ");
            string songName = Console.ReadLine();
            SetPropertyValue(o, "SongName", songName);
 
            // Get the artist and set the Artist property
            Console.Write("Artist: ");
            string artist = Console.ReadLine();
            SetPropertyValue(o, "Artist", artist);
 
            // Get the filetype and set the FileType property.  Not how we have to first
            // get the corresponding Enum value before setting it
            Console.Write("File Type: ");
            string fileTypeName = Console.ReadLine();
            Type fileTypeEnum = assembly.GetType("ChordFileGenerator.FileType");
            object fileTypeValue = GetEnumValue(fileTypeEnum, fileTypeName);
            SetPropertyValue(o, "FileType", fileTypeValue);
 
            // Keep looping and adding music lines
            string lineType;
            do
            {
                // Get the type of line the user wants to enter
                Console.Write("Line Type - Lyric(L), Chords &amp;amp; Lyric (CL) or blank to cancel: ");
                lineType = Console.ReadLine();
 
                if (lineType == "L")
                {
                    // If the user only wants to enter a line, call the AddLine method
                    // that only takes one string parameter
                    string lyrics = Console.ReadLine();
 
                    InvokeMethod(o, "AddLine", new object[] { lyrics });
                }
                else if (lineType == "CL")
                {
                    // If the user wants to enter a chord and line, call the AddLine
                    // method that takes two string parameters
                    string chords = Console.ReadLine();
                    string lyrics = Console.ReadLine();
 
                    InvokeMethod(o, "AddLine", new object[] { chords, lyrics });
                }
            } while (lineType == "L" || lineType == "CL");
 
            // Invoked the SaveFile method with one parameter specifiying the file name
            InvokeMethod(o, "SaveFile", new object[] { "test.txt" });
 
            Console.WriteLine("Saved file!");
            Console.ReadKey();
        }
 
        // ... rest of our methods
    }
}

Example usage of this program is as follows:

Song Name: Our God
Artist: Chris Tomlin
File Type: Chords
Line Type - Lyric(L), Chords & Lyric (CL) or blank to cancel: L
Verse 1:
Line Type - Lyric(L), Chords & Lyric (CL) or blank to cancel: CL
Em        C          G      D
Water you turned into wine
Line Type - Lyric(L), Chords & Lyric (CL) or blank to cancel: CL
Em         C           G
opened the eyes of the blind
Line Type - Lyric(L), Chords & Lyric (CL) or blank to cancel: CL
                  Am            D
there's none like you none like you
Line Type - Lyric(L), Chords & Lyric (CL) or blank to cancel:
Saved file!

This produces a test.txt file in our build folder with the following contents:

Song Name: Our God
Artist: Chris Tomlin
Type: Chords
--------------------------------------------------
Verse 1:
Em        C          G      D
Water you turned into wine
Em         C           G
opened the eyes of the blind
                  Am            D
there's none like you none like you

Conclusion

I hope this article has helped you understand the power of Reflection in C#. The ability to load assemblies dynamically at runtime is an incredibly powerful feature of .NET. This is just one of the ways you can use Reflection in C#.

  • LinkedIn
  • Tumblr
  • Reddit
  • Google+
  • Pinterest
  • Pocket